<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
	<channel>
		<title>Studio Untagged</title>
		<link>https://studiountagged.top</link>
		<description>Studio Untagged 的最新文章订阅源</description>
		<language>zh-cn</language>
		<lastBuildDate>Tue, 08 Sep 2026 14:42:17 GMT</lastBuildDate>
		
				<item>
					<title>Markdown 文档测试</title>
					<link>https://studiountagged.top/articles/test</link>
					<guid isPermaLink="true">https://studiountagged.top/articles/test/</guid>
					<pubDate>Wed, 31 Dec 1969 16:00:00 GMT</pubDate>
					<author>Steven Liu</author>
					
					<description>
						<![CDATA[
							Markdown 文档排版效果测试
A typography test for markdown document.
							
						]]>
					</description>
					<content:encoded>
						<![CDATA[
							<html><head></head><body><h1 id="1-一级标题" tabindex="-1">1. 一级标题</h1>
<h2 id="1-1-二级标题" tabindex="-1">1.1 二级标题</h2>
<h3 id="1-1-1-三级标题" tabindex="-1">1.1.1 三级标题</h3>
<h2 id="2-行内文本样式" tabindex="-1">2. 行内文本样式</h2>
<p>这是一个段落。滚滚长江东逝水，浪花淘尽英雄。是非成败转头空，青山依旧在，几度夕阳红。白发渔樵江渚上，惯看秋月春风。一壶浊酒喜相逢，古今多少事，都付笑谈中。</p>
<ul>
<li><strong>加粗 Bold</strong></li>
<li><em>斜体 Italic</em></li>
<li><em><strong>粗斜体 Bold Italic</strong></em></li>
<li><s>删除线 Strikethrough</s></li>
<li><u>下划线 Underline</u></li>
<li><code>行内代码 Inline Code</code></li>
<li><a href="https://example.com" class="external-link" target="_blank">超链接 Hyperlink</a></li>
<li>行内公式 Inline Formula <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">E = mc^2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.0576em;">E</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8141em;"></span><span class="mord mathnormal">m</span><span class="mord"><span class="mord mathnormal">c</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span></li>
<li><mark>文本高亮 Text Highlight</mark></li>
<li>下标 Subscript H~2~O</li>
<li>上标 Superscript x^2^</li>
<li><q>行内引用 Inline Quote</q></li>
</ul>
<h2 id="3-引用与嵌套" tabindex="-1">3. 引用与嵌套</h2>
<blockquote>
<p>这是一个块引用。</p>
<p>由于要测试背景引号的效果，所以在这里水字数多弄几行。是非成败转头空，青山依旧在，惯看秋月春风。一壶浊酒喜相逢，古今多少事，滚滚长江东逝水，浪花淘尽英雄。 几度夕阳红。白发渔樵江渚上，都付笑谈中。</p>
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.</p>
<blockquote>
<p>这是一个嵌套引用。</p>
<ul>
<li>引用中的列表项</li>
<li>第二项</li>
</ul>
</blockquote>
</blockquote>
<div class="callout callout-red" data-callout-type="danger">
	<div class="callout-content">
		<div class="callout-title">
			<iconify-icon icon="material-symbols:cancel-outline-rounded"></iconify-icon>
			<span>
				危险 DANGER
			</span>
		</div>
		<div class="callout-body"><p>这是个表明危险的块引用。
This is a callout block to show danger.</p>
</div></div></div>
<div class="callout callout-amber" data-callout-type="warning">
	<div class="callout-content">
		<div class="callout-title">
			<iconify-icon icon="material-symbols:warning-outline-rounded"></iconify-icon>
			<span>
				警告 WARNING
			</span>
		</div>
		<div class="callout-body"><p>这是个表明警告的块引用。
This is a callout block to show warnings.</p>
</div></div></div>
<div class="callout callout-green" data-callout-type="success">
	<div class="callout-content">
		<div class="callout-title">
			<iconify-icon icon="material-symbols:check-rounded"></iconify-icon>
			<span>
				成功 SUCCESS
			</span>
		</div>
		<div class="callout-body"><p>这是个表明成功的块引用。
This is a callout block to show success.</p>
</div></div></div>
<div class="callout callout-teal" data-callout-type="tip">
	<div class="callout-content">
		<div class="callout-title">
			<iconify-icon icon="material-symbols:lightbulb-2-outline-rounded"></iconify-icon>
			<span>
				提示 TIP
			</span>
		</div>
		<div class="callout-body"><p>这是个表明危提示的块引用。
This is a callout block to show tips.</p>
</div></div></div>
<div class="callout callout-sky" data-callout-type="note">
	<div class="callout-content">
		<div class="callout-title">
			<iconify-icon icon="material-symbols:info-outline-rounded"></iconify-icon>
			<span>
				注释 NOTE
			</span>
		</div>
		<div class="callout-body"><p>这是个表明注释的块引用。
This is a callout block to show notes.</p>
</div></div></div>
<div class="callout callout-indigo" data-callout-type="abstract">
	<div class="callout-content">
		<div class="callout-title">
			<iconify-icon icon="material-symbols:content-paste-rounded"></iconify-icon>
			<span>
				概述 ABSTRACT
			</span>
		</div>
		<div class="callout-body"><p>这是个表明概述的块引用。
This is a callout block to show abstract.</p>
</div></div></div>
<div class="callout callout-purple" data-callout-type="question">
	<div class="callout-content">
		<div class="callout-title">
			<iconify-icon icon="material-symbols:help-outline-rounded"></iconify-icon>
			<span>
				疑问 QUESTION
			</span>
		</div>
		<div class="callout-body"><p>这是个表明疑问的块引用。
This is a callout block to show questions.</p>
</div></div></div>
<h2 id="4-列表样式-list-style" tabindex="-1">4. 列表样式 List Style</h2>
<h3 id="无序列表" tabindex="-1">无序列表</h3>
<ul>
<li>苹果</li>
<li>香蕉
<ul>
<li>青香蕉</li>
<li>熟香蕉</li>
</ul>
</li>
<li>樱桃</li>
</ul>
<h3 id="有序列表" tabindex="-1">有序列表</h3>
<ol>
<li>第一步</li>
<li>第二步
<ol>
<li>子步骤 1</li>
<li>子步骤 2</li>
</ol>
</li>
</ol>
<h3 id="任务列表" tabindex="-1">任务列表</h3>
<ul class="task-list-container">
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" id="task-item-0" checked="checked" disabled="disabled"><label class="task-list-item-label" for="task-item-0"> 启动网站</label></li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" id="task-item-1" disabled="disabled"><label class="task-list-item-label" for="task-item-1"> 添加文章</label></li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" id="task-item-2" disabled="disabled"><label class="task-list-item-label" for="task-item-2"> 部署生产环境</label></li>
</ul>
<h2 id="5-代码块-code-block" tabindex="-1">5. 代码块 Code Block</h2>
<h3 id="行内代码-inline-code" tabindex="-1">行内代码 Inline Code</h3>
<p>请使用 <code>npm install</code> 安装依赖。</p>
<h3 id="块代码-code-block" tabindex="-1">块代码 Code Block</h3>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">JavaScript</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-JavaScript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">greet</span>(<span class="hljs-params">name</span>) {
  <span class="hljs-keyword">return</span> <span class="hljs-string">`Hello, <span class="hljs-subst">${name}</span>!`</span>;
}
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-title function_">greet</span>(<span class="hljs-string">"World"</span>));
</code></pre>
	</div>
</div>

<h2 id="6-表格测试" tabindex="-1">6. 表格测试</h2>
<div class="table-container"><table>
<thead>
<tr>
<th>编号</th>
<th>项目</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>标题样式</td>
<td>多级标题支持</td>
</tr>
<tr>
<td>2</td>
<td>列表测试</td>
<td>有序/无序/任务</td>
</tr>
<tr>
<td>3</td>
<td>代码样式</td>
<td>高亮 &amp; 缩进</td>
</tr>
</tbody>
</table>
</div><h2 id="7-图片" tabindex="-1">7. 图片</h2>
<h3 id="网络图片" tabindex="-1">网络图片</h3>
<p></p><figure>
	<img src="https://studiountagged.top/articles/test/img/Tsunami_by_hokusai_19th_century.jpg" alt="《神奈川冲浪里》。由日本浮世绘画家葛饰北斋于 1831 年末创作。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>《神奈川冲浪里》。由日本浮世绘画家葛饰北斋于 1831 年末创作。</span>
		</figcaption>
	
</figure><p></p>
<h2 id="8-分割线" tabindex="-1">8. 分割线</h2>
<hr>
<h2 id="9-数学公式-块级-" tabindex="-1">9. 数学公式（块级）</h2>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msubsup><mo>∫</mo><mi>a</mi><mi>b</mi></msubsup><mi>f</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mi mathvariant="normal">d</mi><mi>x</mi><mo>=</mo><mi>F</mi><mo stretchy="false">(</mo><mi>b</mi><mo stretchy="false">)</mo><mo>−</mo><mi>F</mi><mo stretchy="false">(</mo><mi>a</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\int_{a}^{b} f(x) \mathrm{d} x = F(b) - F(a)
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.511em;vertical-align:-0.9119em;"></span><span class="mop"><span class="mop op-symbol large-op" style="margin-right:0.4445em;position:relative;top:-0.0011em;">∫</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.599em;"><span style="top:-1.7881em;margin-left:-0.4445em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">a</span></span></span></span><span style="top:-3.8129em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">b</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.9119em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mclose">)</span><span class="mord mathrm">d</span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.1389em;">F</span><span class="mopen">(</span><span class="mord mathnormal">b</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.1389em;">F</span><span class="mopen">(</span><span class="mord mathnormal">a</span><span class="mclose">)</span></span></span></span></span></p>
<h2 id="10-脚注" tabindex="-1">10. 脚注<sup class="footnote-ref"><a href="#footnote1">[1]</a><a class="footnote-anchor" id="footnote-ref1"></a></sup></h2>
<p>这是包含脚注的文本。脚注位置如下方定义。</p>
<h2 id="11-目录测试-toc-" tabindex="-1">11. 目录测试（TOC）</h2>
<blockquote>
<p>如果你的渲染器支持目录（例如<code>[toc]</code>或<code><a href="/articles/toc" class="internal-link">toc</a></code>），可使用如下语法：</p>
</blockquote>
<p>[toc]</p>
<h2 id="12-定义列表-支持的-markdown-扩展-" tabindex="-1">12. 定义列表（支持的 Markdown 扩展）</h2>
<p>术语 1
: 定义内容 1</p>
<p>术语 2
: 定义内容 2，支持多行<br>
第二行内容</p>
<h2 id="13-mermaid-流程图" tabindex="-1">13. Mermaid 流程图</h2>
<pre class="mermaid" data-processed="false">flowchart TD
    A[Christmas] --&gt;|Get money| B(Go shopping)
    B --&gt; C{Let me think}
    C --&gt;|One| D[Laptop]
    C --&gt;|Two| E[iPhone]
    C --&gt;|Three| F[fa:fa-car Car]
</pre><h2 id="14-音频播放器" tabindex="-1">14. 音频播放器</h2>
<p></p><div class="audio-container">
	<audio class="player-body" src="/articles/test/audio/Prelude_and_Fugue_in_C_major,_BWV_846.mp3" preload="metadata" aria-label="Prelude and Fugue in C major, BWV 846.mp3">
		<source src="/articles/test/audio/Prelude_and_Fugue_in_C_major,_BWV_846.mp3" type="audio/mpeg">
	</audio>

	<div class="audio-controls">
		<button class="play-pause-btn" aria-label="播放">
			<iconify-icon icon="material-symbols:play-arrow-rounded"></iconify-icon>
		</button>
		<div class="audio-info">
			<div class="title-container">
				<div class="title-track">
					<span class="audio-title">Prelude and Fugue in C major, BWV 846.mp3</span>
					<span class="audio-title duplicate" aria-hidden="true">Prelude and Fugue in C major, BWV 846.mp3</span>
				</div>
			</div>
			<small class="audio-state">PAUSED</small>
		</div>
		<div class="progress-section">
			<div class="progress-bar">
				<span class="time current" aria-hidden="true">0:00</span>
				<div class="seek-bar" role="slider" aria-label="播放进度条" aria-valuetext="播放进度，总长 0 分 0 秒" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
						<div class="seek-bar-bg"></div>
						<div class="seek-bar-fill"></div>
						<div class="seek-bar-handle"></div>
				</div>
				<span class="time total" aria-hidden="true">0:00</span>
			</div>
		</div>
		<div class="secondary-section">
			<button class="speed-btn" aria-label="播放速度: 1.0 倍">1.0x</button>
			<button class="mute-btn" aria-label="静音"><iconify-icon icon="material-symbols:volume-up-rounded"></iconify-icon></button>
			<button class="download-btn" aria-label="下载音频">
				<iconify-icon icon="material-symbols:download-rounded"></iconify-icon>
			</button>
		</div>
	</div>
</div><p></p>
<h2 id="15-总结" tabindex="-1">15. 总结</h2>
<p>这是一篇用于全面测试 Markdown 排版效果的文档。你可以：</p>
<ul>
<li>预览渲染样式</li>
<li>检查公式、脚注、引用渲染是否正确</li>
<li>验证高亮和主题切换是否影响文本可读性</li>
</ul>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="footnote1" class="footnote-item"><p>这是脚注的内容。 <a href="#footnote-ref1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
</body></html>
						]]>
					</content:encoded>
				</item>
			

				<item>
					<title>8-bit 计算机搭建笔记（一）：从零开始构建</title>
					<link>https://studiountagged.top/articles/2026-09-07-Build-a-Computer-from-Scratch</link>
					<guid isPermaLink="true">https://studiountagged.top/articles/2026-09-07-Build-a-Computer-from-Scratch/</guid>
					<pubDate>Sun, 06 Sep 2026 16:00:00 GMT</pubDate>
					<author>Steven Liu</author>
					
					<description>
						<![CDATA[
							从元件选型、原理图设计、PCB 绘制，到元件采购和实际焊接，构建一台 8-bit 计算机。
Build an 8-bit computer from component selection and schematic design, to PCB layout, component procurement, and actual soldering.
							
						]]>
					</description>
					<content:encoded>
						<![CDATA[
							<html><head></head><body><h2 id="前言" tabindex="-1">前言</h2>
<p>很早以前，受 The 8-Bit Guy 的影响，我对复古计算机产生了浓厚的兴趣。它们简单、纯粹，有着一种难以言说的魅力。相比之下，现代计算机虽然拥有远超当年的性能，却往往被层层封装在复杂的软硬件体系之中，很难再让人直观地感受到「一台计算机究竟是如何工作的」。</p>
<p>于是，我也产生了自己搭建一台 8-bit 计算机的想法。不过，这个想法断断续续地存在了很长时间，直到最近才终于开始真正着手实现这个项目。</p>
<p>项目的最终目标如下：</p>
<ol>
<li>使用真正的 8-bit CPU，而非 MCU 模拟；</li>
<li>保持电路结构简单、容易理解、调试和维修；</li>
<li>使用仍在生产中的元件；</li>
<li>可编程：
<ol>
<li>即插即用；</li>
<li>可运行游戏和图形软件；</li>
<li>支持直接在电脑上编写汇编和更高级的编程语言（BASIC，C 等）；</li>
<li>能够输出图像（LCD1602，LCD12864，VGA 等）；</li>
<li>能够输出音频（三角波、方波、锯齿波、噪声、DPCM 等）；</li>
</ol>
</li>
<li>元件：
<ol>
<li>使用易于手工焊接的直插式元件，或配合直插式插座；</li>
<li>易于拓展（RAM，ROM，其他 IO 设备等等）；</li>
</ol>
</li>
<li>其他：
<ol>
<li>支持 PS/2 的键盘和鼠标接口；</li>
<li>支持 SD 卡，能够读写文件；</li>
<li>支持 UART, I2C 等与外部设备进行串口通信；</li>
</ol>
</li>
</ol>
<p>不过，现在还不会直接实现上述所有功能。对于这样一个项目而言，一开始就把所有模块放在一起，既难以设计，也很难在出现问题时确定究竟是哪一部分出了故障。因此，我决定先从一个最小系统开始。</p>
<p>这个最小系统并不需要键盘、显示器，也不需要复杂的外围设备。它只需要完成一件最基本的事情：让 CPU 从 ROM 中读取程序、执行指令，并通过地址总线和数据总线与外部电路进行交互。只要这一步能够稳定工作，后续的 RAM、I/O、视频、音频以及其他外围设备，都可以在此基础上逐步加入。这就是整个项目的起点，也是第一版原型机 v1.0.0 所要完成的目标。</p>
<h2 id="元件选型" tabindex="-1">元件选型</h2>
<p>要说 8-bit 计算机时代使用最广为人知的 CPU 之一，非 MOS 6502 莫属了。凭借低廉的价格、简洁的架构和出色的性能，它在推出后迅速获得了广泛应用，与 Zilog Z80 促成了 80 年代的家用计算机革命，从 Apple II、Commodore 64 到 Atari 2600，都能看到 6502 家族的身影。<sup class="footnote-ref"><a href="#footnote1">[1]</a><a class="footnote-anchor" id="footnote-ref1"></a></sup></p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-09-07-Build-a-Computer-from-Scratch/img/MOS_6502AD_4585_top.avif" alt="MOS 6502. 采用 DIP-40 塑料封装。Wikipedia" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>MOS 6502. 采用 DIP-40 塑料封装。<a href="https://commons.wikimedia.org/wiki/File:MOS_6502AD_4585_top.avif#/media/File:MOS_6502AD_4585_top.avif" class="external-link" target="_blank">Wikipedia</a></span>
		</figcaption>
	
</figure><p></p>
<p>如今，最初的 NMOS 版 6502 早已停止生产，不过 WDC (Western Design Center) 在 1981 年推出了 CMOS 版的 W65C02S，在保留原有指令集和架构的基础上，对部分设计缺陷进行了修正，同时降低了功耗，并增加了一些新的指令。更重要的是，它至今仍在生产，使得我们今天依然可以使用一颗全新的 6502 系列 CPU 来搭建一台复古风格的计算机。</p>
<p>因此，本项目最终选择了 WDC 的 W65C02S 作为 CPU。它基本保留了经典 6502 的总线结构，同时采用现代 CMOS 工艺，非常适合作为这台 8 位计算机的核心。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-09-07-Build-a-Computer-from-Scratch/img/W65C02S_top.avif" alt="W65C02S 的引脚示意图。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>W65C02S 的引脚示意图。</span>
		</figcaption>
	
</figure><p></p>
<p>有了 CPU 以后，它该如何运行程序呢？W65C02S 复位后，会从地址 <code>$FFFC</code> 和 <code>$FFFD</code> 读取 Reset Vector，从中获得程序的起始地址，然后跳转到该地址开始执行（详见 W65C02S 手册描述 <code>RESB</code> 引脚功能的部分）。因此，系统中必须存在一块能够在上电后立即提供数据的 ROM (Read-Only Memory, 只读存储器)。</p>
<p>这里选择的是 AT28C256。这是一颗容量为 32 KB 的并行 EEPROM (Electrically Erasable Programmable Read-Only Memory, 带电可擦可编程只读存储器)，拥有 15 根地址线 <code>A0-A15</code> 和 8 根数据线 <code>D0-D7</code>，可以直接连接到 W65C02S 的地址总线和数据总线。与需要专用控制器才能访问的现代 SPI Flash 相比，并行 EEPROM 的接口非常直观，CPU 可以直接通过地址总线访问其中的数据。这也更符合本项目希望保持简单、透明的设计理念。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-09-07-Build-a-Computer-from-Scratch/img/AT28C256_top.avif" alt="AT28C256 的引脚示意图。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>AT28C256 的引脚示意图。</span>
		</figcaption>
	
</figure><p></p>
<p>另一个选择的原因是它仍然比较容易获得，而且支持电擦除和重新编程。在开发阶段，可以很方便地修改程序、重新烧录，然后立即放回计算机进行测试。相比之下，传统的 EPROM (Erasable Programmable Read-Only Memory, 可擦除可编程只读存储器) 通常需要使用紫外线擦除，擦除过程不仅耗时，还需要专用的紫外线擦除设备，因此在反复修改程序的开发阶段远不如 EEPROM 方便。对于这台以学习和实验为目的的原型机来说，这种方式比追求更高的存储密度更加合适。</p>
<p>有了 ROM，CPU 已经可以获得并执行程序，但这还不足以构成一个实用的计算机系统。程序在运行过程中还需要保存变量、临时数据以及运行时状态，因此还需要一块可以随时读写的存储器，也就是 RAM (Random Access Memory, 随机存取存储器)。</p>
<p>本项目选择的是 62256 SRAM。它同样拥有 15 根地址线和 8 根数据线，容量为 32 KB，与前面的 AT28C256 类似，可以非常直接地连接到 W65C02S。SRAM 不需要擦除或编程过程，可以直接进行读写，非常适合作为程序运行时的工作内存。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-09-07-Build-a-Computer-from-Scratch/img/62256_top.avif" alt="62256 的引脚示意图。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>62256 的引脚示意图。</span>
		</figcaption>
	
</figure><p></p>
<p>如果只有 CPU、ROM 和 RAM，即使程序已经正常运行，我们也很难从外部直接观察到计算机正在做什么。因此，需要一个最简单的输出设备，让 CPU 可以把数据「展示」出来。这里没有使用复杂的显示控制器，而是直接使用 8 个 LED 作为最原始的输出设备。</p>
<p>不过，W65C02S 的数据总线并不能直接很好地驱动 LED，因此在 CPU 和 LED 之间增加了一颗 74HC574。它是一个 8 位 D 触发器，可以在时钟信号的控制下锁存数据总线上的 8 个比特，并持续保持输出状态。这样，CPU 只需要向特定的地址执行一次写操作，74HC574 就可以把数据锁存下来，并通过 8 个 LED 显示出来。</p>
<p>除此之外还有一些 74 系列的芯片充当逻辑胶水，用于进行地址解码和反相、消抖等。稍后将详细介绍。</p>
<h2 id="地址空间" tabindex="-1">地址空间</h2>
<p>W65C02S 拥有 <code>A0-A15</code> 共 16 根地址线，因此它可以访问从 <code>$0000</code> 到 <code>$FFFF</code> 的地址，总共 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mn>2</mn><mn>16</mn></msup><mo>=</mo><mn>64</mn><mtext>&nbsp;KB</mtext></mrow><annotation encoding="application/x-tex">2^{16}=64\text{ KB}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8141em;"></span><span class="mord"><span class="mord">2</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">16</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord">64</span><span class="mord text"><span class="mord">&nbsp;KB</span></span></span></span></span> 的空间。对于 CPU 来说，RAM、ROM 以及 I/O 设备本质上都是可以通过地址访问的资源。CPU 并不关心某个地址背后究竟连接的是哪一种芯片，具体由什么设备响应某个地址，是由外围电路决定的。</p>
<p>v1.0.0 的地址空间采用了如下划分方式：</p>
<div class="table-container"><table>
<thead>
<tr>
<th>地址范围</th>
<th>大小</th>
<th>设备</th>
<th>用途</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>$0000-$7FFF</code></td>
<td>32 KB</td>
<td>62256 SRAM</td>
<td>系统 RAM</td>
</tr>
<tr>
<td><code>$8000-$BFFF</code></td>
<td>16 KB</td>
<td>I/O</td>
<td>外围设备</td>
</tr>
<tr>
<td><code>$C000-$FFFF</code></td>
<td>16 KB</td>
<td>AT28C256 EEPROM</td>
<td>程序代码及系统 ROM</td>
</tr>
</tbody>
</table>
</div><p>在 6502 系统中，有两个比较特殊的区域。首先是<strong>零页（Zero Page）</strong>，位于地址 <code>$0000–$00FF</code>。它并不是一个独立的存储器，而是 RAM 的前 256 个字节。它之所以特殊，是因为 6502 为零页提供了专门的寻址方式。访问普通地址时，指令通常需要在操作码之后提供完整的 16 位地址，例如：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">65C02 Assembly</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-65C02 Assembly"><span class="hljs-keyword">LDA</span> <span class="hljs-number">$1234</span>    <span class="hljs-comment">; Load Accumulator with value from $1234</span>
</code></pre>
	</div>
</div>

<p><code>LDA</code> 即 LoaD Accumulator with memory, 将内存载入累加器。这条指令需要同时编码地址的低字节 <code>$34</code> 和高字节 <code>$12</code>。而当访问零页中的地址时，只需要提供一个 8 位地址：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">65C02 Assembly</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-65C02 Assembly"><span class="hljs-keyword">LDA</span> <span class="hljs-number">$34</span>    <span class="hljs-comment">; Load Accumulator with value from $0034</span>
</code></pre>
	</div>
</div>

<p>CPU 会自动将其解释为 <code>$0034</code>。因此，零页寻址不仅可以减少指令长度，也通常可以减少执行所需的时钟周期。对于 6502 来说，零页相当于一组访问速度较快、指令编码更紧凑的工作寄存器，常用于保存临时变量、指针和中间结果。</p>
<p>其次是<strong>堆栈（Stack）</strong>，位于 <code>$0100–$01FF</code>，大小为 256 字节。6502 使用一个 8 位的堆栈指针寄存器 <code>S</code> 来记录堆栈位置，但堆栈的高字节固定为 <code>$01</code>。因此，当堆栈指针为 <code>$FF</code> 时，栈顶对应 <code>$01FF</code>；执行一次入栈操作后，堆栈指针递减，下一项数据会写入更低的地址。相反，出栈操作会先递增堆栈指针，再从对应地址读取数据。用于保存返回地址、寄存器状态以及其他临时数据。</p>
<p>例如，执行 <code>PHA</code> (PusH Accumulator on stack, 将累加器压入栈) 指令时，CPU 会将累加器 <code>A</code> 的内容写入当前栈顶，然后调整堆栈指针；执行 <code>PLA</code> (PuLl Accumulator from stack, 从栈拉出至累加器) 时，则会从堆栈中取回之前保存的内容。调用子程序时，<code>JSR</code> (Jump to new location Saving Return/Jump to SubRoutine, 跳转至新位置并保存返回地址/跳转到子程序) 会将返回地址压入堆栈，之后 <code>RTS</code> 再从堆栈中取出这个地址并返回。发生中断时，CPU 也会利用堆栈保存返回地址和处理器状态。</p>
<p>因此，虽然从容量上看，零页和堆栈都只是 RAM 中很小的一部分，但它们是 6502 程序正常运行所依赖的基础区域。将完整的 32 KB RAM 放在 <code>$0000–$7FFF</code>，可以让零页和堆栈自然地落在正确的位置，同时为程序提供足够的普通工作内存。</p>
<p>接下来是位于地址空间最高端的 16 KB ROM。虽然使用的 AT28C256 实际容量为 32 KB，但在这个版本中，为了简化地址译码难度，只需要将其中一部分映射到 CPU 的地址空间。更重要的是，W65C02S 的复位、中断向量等位于 <code>$FFF0–$FFFF</code>，因此 ROM 必须覆盖这一地址区域。将 ROM 放在 <code>$C000–$FFFF</code>，正好可以满足这一要求。</p>
<p>剩下的中间部分则被预留给 I/O。与 RAM 和 ROM 不同，I/O 地址并不对应实际的存储单元，而是由外围设备对特定地址进行响应。例如，向 LED 所对应的地址执行一次写操作时，译码电路会识别这次访问，并让 74HC574 锁存数据总线上的内容。</p>
<p>之所以预留整整 16 KB 给 I/O，是因为这台计算机并不打算只拥有一个简单的 LED 输出。后续还需要加入 VIA、UART、键盘、存储设备以及其他外围电路，因此提前划出一块独立的 I/O 地址空间，可以让这些设备在后续版本中逐步加入，而不需要频繁修改 RAM 和 ROM 的基本布局。</p>
<h2 id="地址译码" tabindex="-1">地址译码</h2>
<p>确定了地址空间之后，还需要让实际的硬件能够按照这个划分工作。</p>
<p>W65C02S 本身只负责把地址放到地址总线上。对于 CPU 来说，<code>$1234</code>、<code>$8000</code> 和 <code>$C000</code> 都只是一个 16 位地址，它并不知道这个地址对应的是 RAM、I/O 还是 ROM。因此，需要额外的逻辑电路对地址总线进行判断，并根据地址范围产生相应的片选信号（Chip Enable，CE）。</p>
<p>由于地址空间的划分非常规整，因此并不需要复杂的译码逻辑。三个区域的边界分别位于 <code>$8000</code> 和 <code>$C000</code>。将这两个转换为二进制：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mtable rowspacing="0.25em" columnalign="right left" columnspacing="0em"><mtr><mtd class="mtr-glue"></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mo stretchy="false">(</mo><mn>8000</mn><msub><mo stretchy="false">)</mo><mn>16</mn></msub></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><mo stretchy="false">(</mo><mtext>1000&nbsp;0000&nbsp;0000&nbsp;0000</mtext><msub><mo stretchy="false">)</mo><mn>2</mn></msub><mo separator="true">,</mo></mrow></mstyle></mtd><mtd class="mtr-glue"></mtd><mtd class="mml-eqn-num"></mtd></mtr><mtr><mtd class="mtr-glue"></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mo stretchy="false">(</mo><mtext>C000</mtext><msub><mo stretchy="false">)</mo><mn>16</mn></msub></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><mo stretchy="false">(</mo><mtext>1100&nbsp;0000&nbsp;0000&nbsp;0000</mtext><msub><mo stretchy="false">)</mo><mn>2</mn></msub></mrow></mstyle></mtd><mtd class="mtr-glue"></mtd><mtd class="mml-eqn-num"></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{align}
(8000)_{16}&amp;=(\text{1000 0000 0000 0000})_2, \\
(\text{C000})_{16}&amp;=(\text{1100 0000 0000 0000})_2
\end{align}
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.7em;vertical-align:-1.1em;"></span><span class="mtable"><span class="col-align-r"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.6em;"><span style="top:-3.76em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mopen">(</span><span class="mord">8000</span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">16</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.26em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mopen">(</span><span class="mord text"><span class="mord">C000</span></span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">16</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.1em;"><span></span></span></span></span></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.6em;"><span style="top:-3.76em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mopen">(</span><span class="mord text"><span class="mord">1000&nbsp;0000&nbsp;0000&nbsp;0000</span></span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span></span></span><span style="top:-2.26em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mopen">(</span><span class="mord text"><span class="mord">1100&nbsp;0000&nbsp;0000&nbsp;0000</span></span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.1em;"><span></span></span></span></span></span></span></span><span class="tag"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.6em;"><span style="top:-3.6em;"><span class="pstrut" style="height:2.84em;"></span><span class="eqn-num"></span></span><span style="top:-2.1em;"><span class="pstrut" style="height:2.84em;"></span><span class="eqn-num"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.1em;"><span></span></span></span></span></span></span></span></span></p>
<p>只有最高两位发生了变化，这意味着只需要观察地址总线最高的两位 A15 和 A14，就可以确定 CPU 当前访问的是哪一个区域。</p>
<p>A15、A14 的真值表如下：</p>
<div class="table-container"><table>
<thead>
<tr>
<th>A15</th>
<th>A14</th>
<th>地址范围</th>
<th>区域</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>0</code></td>
<td><code>0</code></td>
<td><code>$0000-$3FFF</code></td>
<td>RAM</td>
</tr>
<tr>
<td><code>0</code></td>
<td><code>1</code></td>
<td><code>$4000-$7FFF</code></td>
<td>RAM</td>
</tr>
<tr>
<td><code>1</code></td>
<td><code>0</code></td>
<td><code>$8000-$BFFF</code></td>
<td>IO</td>
</tr>
<tr>
<td><code>1</code></td>
<td><code>1</code></td>
<td><code>$C000-$FFFF</code></td>
<td>ROM</td>
</tr>
</tbody>
</table>
</div><p>因此，三个区域的逻辑关系非常简单：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mtable rowspacing="0.25em" columnalign="right left" columnspacing="0em"><mtr><mtd class="mtr-glue"></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mtext>RAM</mtext></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><mover accent="true"><msub><mi>A</mi><mn>15</mn></msub><mo stretchy="true">‾</mo></mover><mo separator="true">,</mo></mrow></mstyle></mtd><mtd class="mtr-glue"></mtd><mtd class="mml-eqn-num"></mtd></mtr><mtr><mtd class="mtr-glue"></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mtext>I/O</mtext></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><msub><mi>A</mi><mn>15</mn></msub><mo>⋅</mo><mover accent="true"><msub><mi>A</mi><mn>14</mn></msub><mo stretchy="true">‾</mo></mover><mo separator="true">,</mo></mrow></mstyle></mtd><mtd class="mtr-glue"></mtd><mtd class="mml-eqn-num"></mtd></mtr><mtr><mtd class="mtr-glue"></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mtext>ROM</mtext></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><msub><mi>A</mi><mn>15</mn></msub><mo>⋅</mo><msub><mi>A</mi><mn>14</mn></msub><mo separator="true">,</mo></mrow></mstyle></mtd><mtd class="mtr-glue"></mtd><mtd class="mml-eqn-num"></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{align}
	\text{RAM} &amp;= \overline{A_{15}}, \\
	\text{I/O} &amp;= A_{15}\cdot\overline{A_{14}}, \\
	\text{ROM} &amp;= A_{15}\cdot A_{14},
\end{align}
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:4.2867em;vertical-align:-1.8933em;"></span><span class="mtable"><span class="col-align-r"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.3933em;"><span style="top:-4.51em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">RAM</span></span></span></span><span style="top:-2.9667em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">I/O</span></span></span></span><span style="top:-1.4667em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">ROM</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.8933em;"><span></span></span></span></span></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.3933em;"><span style="top:-4.51em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord overline"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8833em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">A</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">15</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.8033em;"><span class="pstrut" style="height:3em;"></span><span class="overline-line" style="border-bottom-width:0.04em;"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span><span class="mpunct">,</span></span></span><span style="top:-2.9667em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord"><span class="mord mathnormal">A</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">15</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord overline"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8833em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">A</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">14</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.8033em;"><span class="pstrut" style="height:3em;"></span><span class="overline-line" style="border-bottom-width:0.04em;"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span><span class="mpunct">,</span></span></span><span style="top:-1.4667em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord"><span class="mord mathnormal">A</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">15</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal">A</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">14</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.8933em;"><span></span></span></span></span></span></span></span><span class="tag"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.3933em;"><span style="top:-4.3933em;"><span class="pstrut" style="height:2.8833em;"></span><span class="eqn-num"></span></span><span style="top:-2.85em;"><span class="pstrut" style="height:2.8833em;"></span><span class="eqn-num"></span></span><span style="top:-1.35em;"><span class="pstrut" style="height:2.8833em;"></span><span class="eqn-num"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.8933em;"><span></span></span></span></span></span></span></span></span></p>
<p>这里还需要注意一个实际电路中的细节：W65C02S 的片选信号以及 AT28C256、62256 等存储器的 CE 输入通常是低电平有效，因此实际电路中可能会使用与上面逻辑表达式相反的有效电平信号。</p>
<p>为了实现这些逻辑，我没有使用专门的地址译码器，而是使用了两块 74HC00。芯片内部包含四个独立的 2 输入 NAND 门。NAND 是一种非常基础的逻辑门，但通过适当的组合，它实际上可以构成 NOT、AND、OR 等其他逻辑功能。再将 A15、A14 以及必要的控制信号组合起来，就可以产生 RAM、I/O 和 ROM 所需要的片选信号。</p>
<p>这里还有一个重要的问题：地址译码本身还不足以判断一次访问究竟是「读」还是「写」。例如，当 CPU 访问 I/O 区域时，LED 只应该在写操作发生时锁存数据，而不能在 CPU 从这个区域读取数据时也产生动作。因此，译码逻辑还需要结合 W65C02S 的 <code>RWB</code> 信号以及时钟信号 <code>PHI2</code>，进一步产生实际的设备控制信号。</p>
<h2 id="读写时序" tabindex="-1">读写时序</h2>
<p>对于 W65C02S 来说，一次内存或 I/O 访问并不是简单地「给出一个地址，然后立即读写数据」。CPU 会按照时钟信号 <code>PHI2</code> 的节拍，在一个个时钟周期内依次完成地址输出、读写控制以及数据传输。外围设备必须在正确的时间响应，否则 CPU 可能读到错误的数据，或者无法正确地将数据写入设备。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-09-07-Build-a-Computer-from-Scratch/img/W65C02S_timing.avif" alt="W65C02S 的时序图。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>W65C02S 的时序图。</span>
		</figcaption>
	
</figure><p></p>
<p>在一个典型的存储器访问周期中，CPU 首先在地址总线上输出目标地址，同时通过 <code>RWB</code> 信号表明当前操作是读取还是写入。地址译码电路根据地址产生对应存储器的片选信号，而存储器则根据自身的控制信号决定是否参与这次总线访问。</p>
<p>当 CPU 执行读取操作时，存储器需要在规定的时间内将对应地址的数据放到数据总线上。CPU 随后在这个总线周期的适当时刻读取数据；而在写入操作中，情况正好相反。CPU 将要写入的数据放到数据总线上，并保持地址、数据以及写控制信号满足存储器的时序要求。存储器在规定的时间窗口内把数据锁存到对应地址。</p>
<p>因此，一次完整的存储器访问实际上同时涉及三类信号：</p>
<ul>
<li>地址总线：决定访问哪个存储地址；</li>
<li>数据总线：在读取时由存储器提供数据，在写入时由 CPU 提供数据；</li>
<li>控制信号：决定当前是否应该访问该设备，以及当前是读取还是写入。</li>
</ul>
<p>译码产生的片选信号也属于控制信号的一部分。只有当某个设备的片选有效时，它才应该对这次总线访问作出响应。</p>
<h3 id="62256-的读取时序" tabindex="-1">62256 的读取时序</h3>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-09-07-Build-a-Computer-from-Scratch/img/62256_read_timing.avif" alt="62256 的读取时序。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>62256 的读取时序。</span>
		</figcaption>
	
</figure><p></p>
<p>时序图上的时间从左向右推进。当 W65C02S 执行一条需要从 RAM 中读取数据的指令时，首先会在地址总线上建立目标地址，对于 SRAM 来说，这个时刻可以作为一次读取周期的起点。这里需要特别区分几个容易混淆的时间参数。62256 的 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>RC</mtext></msub></mrow><annotation encoding="application/x-tex">t_\text{RC}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord text mtight"><span class="mord mtight">RC</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 是 Read Cycle Time，即读取周期时间，它规定了连续两次读取周期之间允许的最小时间。真正表示地址发生变化以后，最迟需要经过多久才能得到有效数据的参数是 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>AA</mtext></msub></mrow><annotation encoding="application/x-tex">t_{\text{AA}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">AA</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>，即 Address Access Time。</p>
<p>在地址建立之后，<code>CE#</code> 和 <code>OE#</code> 也会变为低电平，使 SRAM 被选中并允许输出数据。而数据有效的时间分别受到 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>AA</mtext></msub></mrow><annotation encoding="application/x-tex">t_{\text{AA}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">AA</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>、<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>ACE</mtext></msub></mrow><annotation encoding="application/x-tex">t_{\text{ACE}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">ACE</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 和 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>OE</mtext></msub></mrow><annotation encoding="application/x-tex">t_{\text{OE}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">OE</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 的限制。芯片并不会马上就输出有效数据。它们分别表示从地址变化、<code>CE#</code> 有效以及 <code>OE#</code> 有效开始计算，到数据输出有效所需要的最大时间。因此，这三个参数分别从不同的信号变化时刻开始计时。对于一次正常的读取操作，如果地址、<code>CE#</code> 和 <code>OE#</code> 在相近的时间建立，那么最终能够保证数据有效的时间取决于其中最慢的一条。由手册给出的 AC 电气特性可以得到：</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-09-07-Build-a-Computer-from-Scratch/img/62256_read_characteristics.avif" alt="AS6C62256-55PCN 读取周期的电气特性。请注意，不同厂商生产的 62256 的电气特性可能会略有不同，此处仅作参考。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>AS6C62256-55PCN 读取周期的电气特性。请注意，不同厂商生产的 62256 的电气特性可能会略有不同，此处仅作参考。</span>
		</figcaption>
	
</figure><p></p>
<p>对于这里使用的 AS6C62256-55PCN：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mtable rowspacing="0.25em" columnalign="right left" columnspacing="0em"><mtr><mtd class="mtr-glue"></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><msub><mi>t</mi><mtext>AA</mtext></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><mn>55</mn><mtext>&nbsp;ns</mtext><mo separator="true">,</mo></mrow></mstyle></mtd><mtd class="mtr-glue"></mtd><mtd class="mml-eqn-num"></mtd></mtr><mtr><mtd class="mtr-glue"></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><msub><mi>t</mi><mtext>ACE</mtext></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><mn>55</mn><mtext>&nbsp;ns</mtext><mo separator="true">,</mo></mrow></mstyle></mtd><mtd class="mtr-glue"></mtd><mtd class="mml-eqn-num"></mtd></mtr><mtr><mtd class="mtr-glue"></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><msub><mi>t</mi><mtext>OE</mtext></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><mn>30</mn><mtext>&nbsp;ns</mtext></mrow></mstyle></mtd><mtd class="mtr-glue"></mtd><mtd class="mml-eqn-num"></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{align}
	t_{\text{AA}}&amp;=55\text{ ns},\\
	t_{\text{ACE}}&amp;=55\text{ ns},\\
	t_{\text{OE}}&amp;=30\text{ ns}
\end{align}
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:4.2em;vertical-align:-1.85em;"></span><span class="mtable"><span class="col-align-r"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.35em;"><span style="top:-4.51em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">AA</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">ACE</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.51em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">OE</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.85em;"><span></span></span></span></span></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.35em;"><span style="top:-4.51em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">55</span><span class="mord text"><span class="mord">&nbsp;ns</span></span><span class="mpunct">,</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">55</span><span class="mord text"><span class="mord">&nbsp;ns</span></span><span class="mpunct">,</span></span></span><span style="top:-1.51em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">30</span><span class="mord text"><span class="mord">&nbsp;ns</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.85em;"><span></span></span></span></span></span></span></span><span class="tag"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.35em;"><span style="top:-4.35em;"><span class="pstrut" style="height:2.84em;"></span><span class="eqn-num"></span></span><span style="top:-2.85em;"><span class="pstrut" style="height:2.84em;"></span><span class="eqn-num"></span></span><span style="top:-1.35em;"><span class="pstrut" style="height:2.84em;"></span><span class="eqn-num"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.85em;"><span></span></span></span></span></span></span></span></span></p>
<p>因此，在最坏情况下，从地址稳定开始计算，至少需要经过 55 ns，SRAM 才能保证输出数据已经有效。这里的 55 ns 是器件数据手册给出的最大访问时间，而实际系统设计还需要额外留出一定的时序裕量。</p>
<p>接下来就要看看，W65C02S 能不能给 SRAM 留出足够的时间。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-09-07-Build-a-Computer-from-Scratch/img/W65C02S_timing.avif" alt="W65C02S 的时序图。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>W65C02S 的时序图。</span>
		</figcaption>
	
</figure><p></p>
<p>那么，CPU 究竟会在何时建立好有效地址，又会在何时开始读取数据呢？W65C02S 的时序图初看上去很唬人，因为它将读取、写入以及其他控制信号的时序都综合在了一张图中，所以容易让人感觉无从下手。我们先看 CPU 的读取操作，可以暂时只关注图中与 <code>PHI2</code>、地址总线以及读取数据有关的几行时序。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-09-07-Build-a-Computer-from-Scratch/img/W65C02S_characteristics.avif" alt="W65C02S 的 AC 电气特性。由于 CPU 将采用 +5V 电源供电，所以只需关注蓝色矩形标注的区域即可。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>W65C02S 的 AC 电气特性。由于 CPU 将采用 +5V 电源供电，所以只需关注蓝色矩形标注的区域即可。</span>
		</figcaption>
	
</figure><p></p>
<p>首先来看 <code>PHI2</code>，通过电气特性可以得知，<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>PWH</mtext></msub></mrow><annotation encoding="application/x-tex">t_{\text{PWH}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">PWH</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 和 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>PWL</mtext></msub></mrow><annotation encoding="application/x-tex">t_{\text{PWL}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">PWL</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 分别是时钟高电平和低电平的最小脉冲宽度。在 +5V 供电条件下，两者均为 35 ns，因此对应的最小时钟周期为：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>f</mi><mo>=</mo><mfrac><mn>1</mn><mrow><msub><mi>t</mi><mtext>PWH</mtext></msub><mo>+</mo><msub><mi>t</mi><mtext>PWL</mtext></msub></mrow></mfrac><mo>≈</mo><mn>14</mn><mtext>&nbsp;MHz</mtext></mrow><annotation encoding="application/x-tex">f=\frac{1}{t_{\text{PWH}}+t_{\text{PWL}}}\approx14\text{ MHz}
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.1574em;vertical-align:-0.836em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3214em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">PWH</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">PWL</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.836em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord">14</span><span class="mord text"><span class="mord">&nbsp;MHz</span></span></span></span></span></span></p>
<p>不过我们暂时不需要这么快。由于 W65C02S 内部采用的是静态设计，由于 W65C02S 内部采用静态设计，因此时钟频率可以非常低，理论上甚至可以手动发送时钟信号进行单步调试。在这里我们将使用 2&nbsp;MHz 的晶振，一个完整的时钟周期则为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>CYC</mtext></msub><mo>=</mo><mn>1</mn><mi mathvariant="normal">/</mi><mn>2</mn><mtext>&nbsp;MHz</mtext><mo>=</mo><mn>500</mn><mtext>&nbsp;ns</mtext></mrow><annotation encoding="application/x-tex">t_{\text{CYC}}=1/2\text{ MHz}=500\text{ ns}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">CYC</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1/2</span><span class="mord text"><span class="mord">&nbsp;MHz</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">500</span><span class="mord text"><span class="mord">&nbsp;ns</span></span></span></span></span>.</p>
<p>观察 W65C02S 的 AC 电气特性，其中有几个时间参数需要重点关注：<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>ADS</mtext></msub></mrow><annotation encoding="application/x-tex">t_{\text{ADS}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">ADS</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 地址建立时间，<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>AH</mtext></msub></mrow><annotation encoding="application/x-tex">t_{\text{AH}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">AH</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 地址保持时间，<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>DSR</mtext></msub></mrow><annotation encoding="application/x-tex">t_{\text{DSR}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">DSR</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 读取数据建立时间和 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>DHR</mtext></msub></mrow><annotation encoding="application/x-tex">t_{\text{DHR}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">DHR</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 读取数据保持时间。</p>
<p>这些参数描述的并不是 CPU 内部某个明确可见的操作，而是 CPU 对外部总线信号提出的时序要求。对于地址信号，先找到当前总线周期的参考边沿。以该边沿为界，向前查看 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>ADS</mtext></msub></mrow><annotation encoding="application/x-tex">t_{\text{ADS}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">ADS</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>，向后查看 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>AH</mtext></msub></mrow><annotation encoding="application/x-tex">t_{\text{AH}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">AH</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>，这段时间就是 CPU 要求地址保持有效的区间。也就是说，外部设备应当在这段时间内完成地址译码，并将地址视为稳定输入；对于读取数据，则需要以 CPU 的数据采样时刻为中心进行判断。向前查看 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>DSR</mtext></msub></mrow><annotation encoding="application/x-tex">t_{\text{DSR}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">DSR</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>，向后查看 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>DHR</mtext></msub></mrow><annotation encoding="application/x-tex">t_{\text{DHR}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">DHR</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>，这段时间就是 CPU 要求外部设备提供有效数据的区间。外部设备必须在这段区间内将数据稳定地驱动到数据总线上，CPU 才能可靠地完成读取。</p>
<p>因此，阅读这类时序图时，可以先确定当前总线周期的参考边沿，再分别根据<strong>建立</strong>和<strong>保持</strong>确定有效区间。前者告诉我们地址最迟何时必须建立、最早何时可以撤销；后者则告诉我们外部设备最迟何时必须提供有效数据，以及数据最早何时可以停止保持。</p>
<p>读者可能注意到地址轴上出现了两次 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>AH</mtext></msub></mrow><annotation encoding="application/x-tex">t_{\text{AH}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">AH</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>，读取数据轴上也有类似情况，出现了两次 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>DHR</mtext></msub></mrow><annotation encoding="application/x-tex">t_{\text{DHR}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">DHR</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>. 这是因为前面的其实属于上一个周期。而本周期真正的保持时间是最后面的那一个。由此我们可以得出地址和数据有效的时刻：</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-09-07-Build-a-Computer-from-Scratch/img/W65C02S_timing_2.avif" alt="地址有效的区间位于从 tADSt{\text{ADS}}tADS​ 结束到 tAHt{\text{AH}}tAH​ 结束的时刻；读取有效数据的区间位于从 tDSRt{\text{DSR}}tDSR​ 开始到 tDHRt{\text{DHR}}tDHR​ 结束的时刻。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>地址有效的区间位于从 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>ADS</mtext></msub></mrow><annotation encoding="application/x-tex">t_{\text{ADS}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">ADS</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 结束到 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>AH</mtext></msub></mrow><annotation encoding="application/x-tex">t_{\text{AH}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">AH</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 结束的时刻；读取有效数据的区间位于从 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>DSR</mtext></msub></mrow><annotation encoding="application/x-tex">t_{\text{DSR}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">DSR</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 开始到 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>DHR</mtext></msub></mrow><annotation encoding="application/x-tex">t_{\text{DHR}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">DHR</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 结束的时刻。</span>
		</figcaption>
	
</figure><p></p>
<p>读取数据需要在采样点之前满足 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>DSR</mtext></msub><mo>=</mo><mn>10</mn><mtext>&nbsp;ns</mtext></mrow><annotation encoding="application/x-tex">t_{\text{DSR}}=10\text{ ns}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">DSR</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">10</span><span class="mord text"><span class="mord">&nbsp;ns</span></span></span></span></span> 的建立时间，因此 CPU 的数据采样时刻可以看作 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>sample</mtext></msub><mo>=</mo><mn>500</mn><mo>−</mo><mn>10</mn><mo>=</mo><mn>490</mn><mtext>&nbsp;ns</mtext></mrow><annotation encoding="application/x-tex">t_{\text{sample}}=500-10=490\text{ ns}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.9012em;vertical-align:-0.2861em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">sample</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7278em;vertical-align:-0.0833em;"></span><span class="mord">500</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">10</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">490</span><span class="mord text"><span class="mord">&nbsp;ns</span></span></span></span></span>.</p>
<p>另一方面，CPU 至多仍需 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>ADS</mtext></msub><mo>=</mo><mn>30</mn><mtext>&nbsp;ns</mtext></mrow><annotation encoding="application/x-tex">t_{\text{ADS}}=30\text{ ns}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">ADS</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">30</span><span class="mord text"><span class="mord">&nbsp;ns</span></span></span></span></span> 建立地址。因此，即使按照最不利的情况计算，外部存储器从地址建立到 CPU 采样数据之间仍然有 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>available</mtext></msub><mo>=</mo><mn>490</mn><mo>−</mo><mn>30</mn><mo>=</mo><mn>460</mn><mtext>&nbsp;ns</mtext></mrow><annotation encoding="application/x-tex">t_{\text{available}}=490-30=460\text{ ns}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">available</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7278em;vertical-align:-0.0833em;"></span><span class="mord">490</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">30</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">460</span><span class="mord text"><span class="mord">&nbsp;ns</span></span></span></span></span>，远大于 RAM 所需的 55 ns 的时间。</p>
<p>在完成读取数据操作以后，CPU 还要求数据继续保持 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>DHR</mtext></msub><mo>=</mo><mn>10</mn><mtext>&nbsp;ns</mtext></mrow><annotation encoding="application/x-tex">t_{\text{DHR}}=10\text{ ns}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">DHR</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">10</span><span class="mord text"><span class="mord">&nbsp;ns</span></span></span></span></span> 的时间。与此同时，在完成数据读取之后，CPU 并不会立即改变地址总线上的内容，还将继续保持 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>AH</mtext></msub><mo>=</mo><mn>10</mn><mtext>&nbsp;ns</mtext></mrow><annotation encoding="application/x-tex">t_{\text{AH}}=10\text{ ns}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">AH</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">10</span><span class="mord text"><span class="mord">&nbsp;ns</span></span></span></span></span>；随后，RAM 在地址发生变化以后，数据并不会马上消失，也将保持一小段时间的输出 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>OH</mtext></msub><mo>=</mo><mn>10</mn><mtext>&nbsp;ns</mtext></mrow><annotation encoding="application/x-tex">t_{\text{OH}}=10\text{ ns}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">OH</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">10</span><span class="mord text"><span class="mord">&nbsp;ns</span></span></span></span></span>. 也就是说，从 CPU 完成数据采样开始算起，这两个时间段恰好可以首尾相接，RAM 输出的原数据理论上至少还能保持 20 ns，仍然有约 10 ns 时间裕量。</p>
<h3 id="62256-的写入时序" tabindex="-1">62256 的写入时序</h3>
<p>接下来再看写入时序。除了需要提前建立好地址和数据之外，根据时序图的注释，RAM 的实际写入发生在 <code>CE#</code> 和 <code>WE#</code> 均处于有效电平的重叠期间。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-09-07-Build-a-Computer-from-Scratch/img/62256_write_timing.avif" alt="62256 的写入时序。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>62256 的写入时序。</span>
		</figcaption>
	
</figure><p></p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-09-07-Build-a-Computer-from-Scratch/img/62256_write_characteristics.avif" alt="AS6C62256 写入周期的电气特性。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>AS6C62256 写入周期的电气特性。</span>
		</figcaption>
	
</figure><p></p>
<p>这里存在一个值得注意的时序问题。当前设计中，RAM 的片选信号 <code>CE#</code> 直接由 CPU 的最高位地址线 <code>A15</code> 产生。对于 W65C02S 而言，地址总线、读写控制等信号都在相近的时刻发生变化，CPU 并没有保证外部组合逻辑经过传播延迟之后，最终生成的 <code>CE#</code>、<code>WE#</code> 一定能够以我们希望的先后顺序到达 SRAM。</p>
<p>AS6C62256 的电气特性虽然给出 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>WR</mtext></msub></mrow><annotation encoding="application/x-tex">t_{\text{WR}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">WR</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 和 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>AS</mtext></msub></mrow><annotation encoding="application/x-tex">t_{\text{AS}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">AS</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 的最小值虽然可以达到 0 ns，但这并不意味着实际电路中可以忽略它们。0 ns 只是器件在满足其他时序条件时允许的最小裕量，器件本身没有额外规定的最小地址建立时间和写恢复时间。CPU 的地址输出、<code>RWB</code>、<code>PHI2</code> 以及由这些信号产生的 <code>CE#</code>、<code>WE#</code> 都存在不同的传播延迟，因此在地址正在切换的短暂窗口内，RAM 可能会看到旧地址、新地址以及尚未完全稳定的地址组合。尤其是在写周期中，如果 <code>WE#</code> 在地址尚未稳定时就已经进入有效状态，就可能使 RAM 在错误的地址下产生一个有效写入窗口；如果地址或数据在写入窗口内发生变化，导致不可预测的数据被写入。</p>
<p>那么，如何保证在整个有效写入窗口内，RAM 所看到的地址、数据和控制信号都满足数据手册规定的时序关系？</p>
<p>好，分析了这么一大堆，虽然要意识到问题的存在这一点确实很困难，但是解决方案却非常简单。</p>
<p>对于当前这个设计，最直接的思路就是让写入窗口避开地址总线的过渡过程。观察 W65C02S 的时序图可以发现，地址尚未稳定的阶段基本处于 <code>PHI2</code> 的低电平期间，而建立并保持稳定的阶段基本处于高电平期间。</p>
<p>W65C02S 的 <code>RWB</code> 信号用于表示当前总线周期是读取还是写入。当 <code>RWB=1</code> 时表示读取，当 <code>RWB=0</code> 时表示写入。因此，如果只考虑读写方向，可以用 <code>NOT RWB</code> 表示「当前正在写入」。另一方面，<code>PHI2</code> 可以用来限制写入发生的时间窗口。只有当 <code>PHI2=1</code>，并且当前又是写周期时，RAM 才允许进入写入状态。因此，理想的逻辑表达式就是：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mover accent="true"><mtext>WE</mtext><mo stretchy="true">‾</mo></mover><mo>=</mo><mo stretchy="false">(</mo><mover accent="true"><mtext>RWB</mtext><mo stretchy="true">‾</mo></mover><mo>⋅</mo><mtext>PHI2</mtext><msup><mo stretchy="false">)</mo><mo mathvariant="normal">′</mo></msup></mrow><annotation encoding="application/x-tex">\overline{\text{WE}} = (\overline{\text{RWB}} \cdot \text{PHI2})^\prime
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8833em;"></span><span class="mord overline"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8833em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">WE</span></span></span></span><span style="top:-3.8033em;"><span class="pstrut" style="height:3em;"></span><span class="overline-line" style="border-bottom-width:0.04em;"></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.1333em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord overline"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8833em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">RWB</span></span></span></span><span style="top:-3.8033em;"><span class="pstrut" style="height:3em;"></span><span class="overline-line" style="border-bottom-width:0.04em;"></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1.0519em;vertical-align:-0.25em;"></span><span class="mord text"><span class="mord">PHI2</span></span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8019em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span></span></p>
<p>反相 <code>RWB</code> 只需把与非门的两个输入接在一起即可。这样一来，最终只用了两个与非门就解决了问题。而从 SN74HC00N 手册上的电气特性可以得出，在 4.5V、室温的情况下，信号传播时间的延迟在 9~18ns 以内，和之前计算得到的 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mtext>available</mtext></msub><mo>=</mo><mn>460</mn><mtext>&nbsp;ns</mtext></mrow><annotation encoding="application/x-tex">t_{\text{available}}=460\text{ ns}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord text mtight"><span class="mord mtight">available</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">460</span><span class="mord text"><span class="mord">&nbsp;ns</span></span></span></span></span> 相比仍在允许范围内。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-09-07-Build-a-Computer-from-Scratch/img/74HC00_characteristic.avif" alt="SN74HC00N 的电气特性。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>SN74HC00N 的电气特性。</span>
		</figcaption>
	
</figure><p></p>
<h3 id="at28c256-读取时序" tabindex="-1">AT28C256 读取时序</h3>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-09-07-Build-a-Computer-from-Scratch/img/AT28C256_read_timing.avif" alt="AT28C256-15PU 的读取时序。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>AT28C256-15PU 的读取时序。</span>
		</figcaption>
	
</figure><p></p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-09-07-Build-a-Computer-from-Scratch/img/AT28C256_read_characteristic.avif" alt="AT28C256-15PU 的电气特性。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>AT28C256-15PU 的电气特性。</span>
		</figcaption>
	
</figure><p></p>
<p>AT28C256 的读取时序与前面分析的 62256 基本相同。AT28C256 同样属于异步存储器，CPU 通过地址总线提供地址，ROM 根据地址输出对应的数据，而不需要额外的时钟信号。</p>
<p>因此，前面针对 RAM 读取时序所采用的分析方法同样适用于 ROM：只需要确认地址建立之后，ROM 能否在 W65C02S 采样数据之前输出有效数据，并确认数据在采样之后能够保持足够长的时间即可。</p>
<p>AT28C256 的具体访问时间取决于所使用的速度等级，但即使按照较慢的器件规格计算，相对于本项目 2&nbsp;MHz 时钟所提供的 500 ns 周期，这个时间窗口仍然非常充裕。因此，在当前的工作频率下，ROM 的读取速度不会成为系统的时序瓶颈。</p>
<p>在控制信号方面，ROM 与 RAM 略有不同。RAM 既需要读取控制，也需要通过 <code>WE#</code> 控制写入；而 AT28C256 在正常运行时只需要响应相应的片选和输出使能信号即可，暂不考虑对 ROM 进行写入操作。</p>
<p>因此，在完成前面的 RAM 读取时序分析之后，这里不再重复绘制完整的 ROM 读取时序图。只要将 AT28C256 的地址访问时间、<code>CE#</code> 和 <code>OE#</code> 的响应时间与 W65C02S 的数据建立、保持要求进行比较，即可确认当前 2 MHz 的设计具有足够的时序裕量。</p>
<h3 id="74hc574-写入时序" tabindex="-1">74HC574 写入时序</h3>
<p>除了 RAM 和 ROM 以外，还有一个需要特别处理的元件即 74HC574.</p>
<p>74HC574 是一个 8-bit D 触发器。与前面介绍的 RAM 不同，它不是在一段电平有效期间持续进行写入，而是在<strong>时钟的有效边沿</strong>到来时，将数据总线上的内容锁存下来。因此，CPU 若想往其中写入数据，不能简单地给 74HC574 一个持续有效的片选信号，而需要产生一个 <code>CLK</code> 脉冲信号。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-09-07-Build-a-Computer-from-Scratch/img/74HC574_top.avif" alt="74HC574 的引脚示意图。其中 xD 为数据输入，xQ 为数据输出。使能引脚 OE# 低电平有效。CLK 引脚表明该器件在时钟有效边沿上触发。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>74HC574 的引脚示意图。其中 <code>xD</code> 为数据输入，<code>xQ</code> 为数据输出。使能引脚 <code>OE#</code> 低电平有效。<code>CLK</code> 引脚表明该器件在时钟有效边沿上触发。</span>
		</figcaption>
	
</figure><p></p>
<p>将 74HC574 的片选信号和写入条件合并，可以得到最终的逻辑表达式。它只应当在被选中且 CPU 正在执行写操作时锁存数据。由于它是在时钟信号的上升沿触发，因此，时钟信号可以表示为：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>CLK</mtext><mo>=</mo><mtext>LED_CS</mtext><mo>⋅</mo><mtext>WE</mtext></mrow><annotation encoding="application/x-tex">\text{CLK}=\text{LED\_CS}\cdot\text{WE}
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord text"><span class="mord">CLK</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.9933em;vertical-align:-0.31em;"></span><span class="mord text"><span class="mord">LED_CS</span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord text"><span class="mord">WE</span></span></span></span></span></span></p>
<p>其中，<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>LED_CS</mtext></mrow><annotation encoding="application/x-tex">\text{LED\_CS}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.9933em;vertical-align:-0.31em;"></span><span class="mord text"><span class="mord">LED_CS</span></span></span></span></span> 表示当前地址属于 LED 所对应的地址范围。由于当时我手头上正好有两片 74HC00，因此需要将上式转换成通过与非门表达。在当前地址划分方案中， I/O 位于 <code>$8000-$BFFF</code>，而 LED 只需响应其中任意一个地址。因此通过总线中的 <code>A15</code> 和 <code>A14</code> 便可生成片选信号：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mover accent="true"><mtext>LED_CS</mtext><mo stretchy="true">‾</mo></mover><mo>=</mo><msup><mrow><mo fence="true">(</mo><msub><mi>A</mi><mn>15</mn></msub><mo>⋅</mo><mover accent="true"><msub><mi>A</mi><mn>14</mn></msub><mo stretchy="true">‾</mo></mover><mo fence="true">)</mo></mrow><mo mathvariant="normal">′</mo></msup></mrow><annotation encoding="application/x-tex">\overline{\text{LED\_CS}}=\left( A_{15}\cdot\overline{ A_{14} } \right)^\prime
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.1933em;vertical-align:-0.31em;"></span><span class="mord overline"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8833em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">LED_CS</span></span></span></span><span style="top:-3.8033em;"><span class="pstrut" style="height:3em;"></span><span class="overline-line" style="border-bottom-width:0.04em;"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.31em;"><span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.3751em;vertical-align:-0.35em;"></span><span class="minner"><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size1">(</span></span><span class="mord"><span class="mord mathnormal">A</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">15</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord overline"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8833em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">A</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">14</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.8033em;"><span class="pstrut" style="height:3em;"></span><span class="overline-line" style="border-bottom-width:0.04em;"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size1">)</span></span></span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:1.0251em;"><span style="top:-3.3362em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span></span></p>
<p>在选中时与非门输出的是低电平，因此需要将其反相：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>LED_CS</mtext><mo>=</mo><msup><mrow><mo fence="true">(</mo><mover accent="true"><mtext>LED_CS</mtext><mo stretchy="true">‾</mo></mover><mo fence="true">)</mo></mrow><mo mathvariant="normal">′</mo></msup></mrow><annotation encoding="application/x-tex">\text{LED\_CS}=\left( \overline{\text{LED\_CS}} \right)^\prime
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.9933em;vertical-align:-0.31em;"></span><span class="mord text"><span class="mord">LED_CS</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.3751em;vertical-align:-0.35em;"></span><span class="minner"><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size1">(</span></span><span class="mord overline"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8833em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">LED_CS</span></span></span></span><span style="top:-3.8033em;"><span class="pstrut" style="height:3em;"></span><span class="overline-line" style="border-bottom-width:0.04em;"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.31em;"><span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size1">)</span></span></span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:1.0251em;"><span style="top:-3.3362em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span></span></p>
<p>而 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>WE</mtext></mrow><annotation encoding="application/x-tex">\text{WE}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord text"><span class="mord">WE</span></span></span></span></span> 是之前生成的写使能经反相后的结果：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mtable rowspacing="0.25em" columnalign="right left" columnspacing="0em"><mtr><mtd class="mtr-glue"></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mtext>WE</mtext></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><msup><mrow><mo fence="true">[</mo><msup><mrow><mo fence="true">(</mo><mover accent="true"><mtext>RWB</mtext><mo stretchy="true">‾</mo></mover><mo>⋅</mo><mtext>PHI2</mtext><mo fence="true">)</mo></mrow><mo mathvariant="normal">′</mo></msup><mo fence="true">]</mo></mrow><mo mathvariant="normal">′</mo></msup></mrow></mstyle></mtd><mtd class="mtr-glue"></mtd><mtd class="mml-eqn-num"></mtd></mtr><mtr><mtd class="mtr-glue"></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><msup><mrow><mo fence="true">(</mo><mover accent="true"><mtext>WE</mtext><mo stretchy="true">‾</mo></mover><mo fence="true">)</mo></mrow><mo mathvariant="normal">′</mo></msup></mrow></mstyle></mtd><mtd class="mtr-glue"></mtd><mtd class="mml-eqn-num"></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{align}
\text{WE}
&amp;=\left[ \left( \overline{\text{RWB}} \cdot \text{PHI2} \right)^\prime \right]^\prime \\
&amp;=\left( \overline{\text{WE}} \right)^\prime
\end{align}
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:3.6269em;vertical-align:-1.5635em;"></span><span class="mtable"><span class="col-align-r"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.0635em;"><span style="top:-4.0635em;"><span class="pstrut" style="height:3.2918em;"></span><span class="mord"><span class="mord text"><span class="mord">WE</span></span></span></span><span style="top:-2.0883em;"><span class="pstrut" style="height:3.2918em;"></span><span class="mord"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.5635em;"><span></span></span></span></span></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.0635em;"><span style="top:-4.0635em;"><span class="pstrut" style="height:3.2918em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="minner"><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size2">[</span></span><span class="minner"><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size1">(</span></span><span class="mord overline"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8833em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">RWB</span></span></span></span><span style="top:-3.8033em;"><span class="pstrut" style="height:3em;"></span><span class="overline-line" style="border-bottom-width:0.04em;"></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord text"><span class="mord">PHI2</span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size1">)</span></span></span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:1.0251em;"><span style="top:-3.3362em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size2">]</span></span></span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:1.2918em;"><span style="top:-3.6029em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span><span style="top:-2.0883em;"><span class="pstrut" style="height:3.2918em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="minner"><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size1">(</span></span><span class="mord overline"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8833em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">WE</span></span></span></span><span style="top:-3.8033em;"><span class="pstrut" style="height:3em;"></span><span class="overline-line" style="border-bottom-width:0.04em;"></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size1">)</span></span></span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:1.0251em;"><span style="top:-3.3362em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.5635em;"><span></span></span></span></span></span></span></span><span class="tag"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.0635em;"><span style="top:-4.0635em;"><span class="pstrut" style="height:3.2918em;"></span><span class="eqn-num"></span></span><span style="top:-2.0883em;"><span class="pstrut" style="height:3.2918em;"></span><span class="eqn-num"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.5635em;"><span></span></span></span></span></span></span></span></span></p>
<h2 id="复位与-nmi" tabindex="-1">复位与 NMI</h2>
<p>最后还有一个不能省略的部分，就是复位和 NMI (Non-Maskable Interrupt, 不可屏蔽中断) 电路。</p>
<p>由于机械按钮不是一个理想的开关。当按键按下或释放时，触点通常会在很短的时间内发生多次快速导通和断开。如果直接把按键连接到 CPU 的控制输入，那么 CPU 可能会把这些瞬间的电平变化误认为多次触发。</p>
<p>因此，这里首先使用 RC 电路对按键产生的快速变化进行滤波。电容会使电压变化变得相对缓慢，从而抑制机械触点产生的高频抖动。随后，再通过施密特触发器将缓慢变化的模拟电压重新转换成干净的数字电平。而施密特反相器与普通的相比，区别在于它具有迟滞特性。输入电压上升和下降时分别使用不同的阈值，因此即使 RC 电路的输出在阈值附近缓慢变化，也不会因为微小的电压波动左右横跳。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-09-07-Build-a-Computer-from-Scratch/img/SN74HC14N_characteristic.avif" alt="SN74HC14N 的电气特性。在室温，电源电压为 4.5V 时，正向阈值典型值 VT+=2.5&nbsp;VV{T+}=2.5\text{ V}VT+​=2.5&nbsp;V，反向阈值典型值 VT−=1.6&nbsp;VV{T-}=1.6\text{ V}VT−​=1.6&nbsp;V. 这两个阈值之间的区间能在一定程度上消除当电平处于中间状态时产生的毛刺。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>SN74HC14N 的电气特性。在室温，电源电压为 4.5V 时，正向阈值典型值 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>V</mi><mrow><mi>T</mi><mo>+</mo></mrow></msub><mo>=</mo><mn>2.5</mn><mtext>&nbsp;V</mtext></mrow><annotation encoding="application/x-tex">V_{T+}=2.5\text{ V}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8917em;vertical-align:-0.2083em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.2222em;">V</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.2222em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.1389em;">T</span><span class="mord mtight">+</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2083em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord">2.5</span><span class="mord text"><span class="mord">&nbsp;V</span></span></span></span></span>，反向阈值典型值 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>V</mi><mrow><mi>T</mi><mo>−</mo></mrow></msub><mo>=</mo><mn>1.6</mn><mtext>&nbsp;V</mtext></mrow><annotation encoding="application/x-tex">V_{T-}=1.6\text{ V}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8917em;vertical-align:-0.2083em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.2222em;">V</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.2222em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.1389em;">T</span><span class="mord mtight">−</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2083em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord">1.6</span><span class="mord text"><span class="mord">&nbsp;V</span></span></span></span></span>. 这两个阈值之间的区间能在一定程度上消除当电平处于中间状态时产生的毛刺。</span>
		</figcaption>
	
</figure><p></p>
<h2 id="最终成品" tabindex="-1">最终成品</h2>
<p>经过前面的地址空间规划、地址译码、读写时序以及复位和 NMI 电路设计，v1.0.0 的整个硬件设计基本确定。这里不再逐一分析每个模块的具体工作原理，而是直接给出完整原理图。它可以作为前面各章节的一个总览，也方便后续调试时根据实际连接关系检查信号。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-09-07-Build-a-Computer-from-Scratch/img/prototype_schematic.avif" alt="v1.0.0 的完整原理图。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>v1.0.0 的完整原理图。</span>
		</figcaption>
	
</figure><p></p>
<p>此后就是 PCB 设计和焊接。相比前面的电路设计，这一部分没有太多值得展开分析的内容，就借这个机会聊聊完成这个项目过程中的一些感受。</p>
<p>这其实是我第一次独自完成一个相对完整的硬件项目，从元件选型、原理图设计、PCB 绘制，到后续的元件采购和实际焊接，基本把整个流程都走了一遍。前前后后花了差不多一个月的时间才最终完成。设计 PCB 和等待打样的那段时间里，几乎每天都在期待 PCB 和各种元件能够尽快送到。毕竟在电脑里反复检查了那么多次的电路，最终能不能真正工作，还得等它变成一块实实在在的电路板才能知道。</p>
<p>PCB 到货以后，第一眼看上去还是比较满意的。实际拿到手里之后，又是另一种感觉：原本只是电脑屏幕上的原理图和 PCB 布局，现在变成了一块可以拿在手里的实体电路板，整体看上去也比预想中更有分量。唯一一点就是丝印的字体太小了，在软件里绘制的时候感觉 10*10 cm 似乎已经挺大了，拿在手里才发现不过才比巴掌宽一圈而已。</p>
<p>接下来就是元件焊接。虽然之前在学校的实习课中学习过基本的焊接方法，但毕竟已经很久没有实际操作，刚开始焊的时候还是有些生疏。因此，最终留下的部分焊点并不算特别美观，有些元件还焊的歪歪斜斜的。不过检查之后，好在没有发现虚焊、短路或者焊点粘连等影响电路正常工作的明显问题。</p>
<p>至此，从最初的设计，到最终变成一块真正可以运行的电路板，v1.0.0 的硬件部分终于完成。前面讨论的地址总线、数据总线、存储器、I/O 和各种控制信号，也将在这里第一次真正同时工作。</p>
<p>不过，焊接完成并不意味着这个项目已经成功。对于一台自己设计的计算机来说，真正的验证还要从第一次上电开始。下一篇文章将进入实际测试阶段，介绍系统的第一次上电、程序烧录，以及程序运行过程中遇到的问题和后续的调试过程。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-09-07-Build-a-Computer-from-Scratch/img/real_prototype.avif" alt="完成焊接后的原型机。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>完成焊接后的原型机。</span>
		</figcaption>
	
</figure><p></p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="footnote1" class="footnote-item"><p>MOS Technology 6502. <em>Wikipedia</em>. <a href="https://en.wikipedia.org/wiki/MOS_Technology_6502" class="external-link" target="_blank">https://en.wikipedia.org/wiki/MOS_Technology_6502</a> <a href="#footnote-ref1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
</body></html>
						]]>
					</content:encoded>
				</item>
			

				<item>
					<title>驱动 AY-3-8910（二）：简单频谱分析以及滤波电路设计</title>
					<link>https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design</link>
					<guid isPermaLink="true">https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/</guid>
					<pubDate>Sat, 25 Apr 2026 16:00:00 GMT</pubDate>
					<author>Steven Liu</author>
					
					<description>
						<![CDATA[
							对 AY-3-8910 的输出信号进行频谱分析，并设计一阶 RC 无源滤波器
Perform a spectral analysis of the AY-3-8910 output signals and design a first-order passive RC filter.
							
						]]>
					</description>
					<content:encoded>
						<![CDATA[
							<html><head></head><body><h2 id="前言" tabindex="-1">前言</h2>
<p>在上一篇文章<a href="/articles/2025-09-28-AY-3-8910" class="internal-link">驱动 AY-3-8910（一）</a>中，已经成功驱动了 AY-3-8910，并让它能够播放指定音量和时长的音调。在这一篇文章中，将介绍频谱分析，以及简单滤波电路设计等内容。</p>
<h2 id="频谱分析" tabindex="-1">频谱分析</h2>
<p>在上一篇文章中，芯片输出信号是直接驱动耳机的，这种未经优化的信号听起来非常刺耳，尤其在高音区。<strong>相比之下，</strong> 同样是高音，为什么像施坦威这类名琴发出的声音，反而显得清脆悦耳、毫无噪感呢？</p>
<p>要回答这个问题，就得先了解声音的基本特征。钢琴弹奏一个 C4 的音，和小提琴拉奏一个 C4 的音，这两者的音高和音量完全一致，就算蒙住眼睛，也能轻易分辨出是哪个乐器发出的。因为它们的<strong>音色 (Timbre)</strong> 有显著不同。音色的本质是什么？为什么不同的乐器会有不同的音色？</p>
<p>以钢琴为例，按下一个键时，会联动一个小木槌敲击琴弦振动发声。如果去看钢琴声音的波形图，放大之后就会发现并非简单的正弦波，而是一种更加复杂的振动模式。因为除了琴弦振动本身带来的正弦波以外，还会有诸如琴弦材质、小木槌的摩擦、腔体的共振乃至空气湿度等等因素影响着最终波形的塑造。</p>
<p>在这个过程中，琴弦本身的振动就是<strong>基频 (Fundamental Fre­quency)</strong>。基频决定了我们听到的音高；除了基频以外的其他所有频率分量就构成了<strong>泛音 (Overtone)</strong>。在泛音中，基频的整数倍 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>2</mn><mi>f</mi><mo separator="true">,</mo><mn>3</mn><mi>f</mi><mo separator="true">,</mo><mn>4</mn><mi>f</mi></mrow><annotation encoding="application/x-tex">2f, 3f, 4f</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord">2</span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">3</span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">4</span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span></span></span></span> 等为<strong>谐音 (Harmonics)</strong>，这是乐器音色的主要决定部分。除此之外还有一些非谐波的成分，例如小木槌的摩擦、腔体共振等等，这些共同构成了钢琴丰富且复杂的音色。而正是因为不同乐器的发声结构千奇百异，才会有独属于它们自己的音色。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/Instrument_Waveforms.avif" alt="不同乐器发出的波形。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>不同乐器发出的波形。</span>
		</figcaption>
	
</figure><p></p>
<h3 id="傅里叶变换" tabindex="-1">傅里叶变换</h3>
<p>知道了构成音色的到底是什么以后，该如何对其进行分析？法国数学家傅里叶 (Joseph Fourier) 揭示了一个深刻的真理：<strong>任何复杂的信号波形，本质上都可以分解为无数个不同频率正弦波的线性叠加</strong>。这一数学工具便是名垂青史的<strong>傅里叶变换 (Fourier Transform, FT)</strong>。以钢琴弹奏出的某个音符为例，宏观上看，它表现为一种极度复杂的振动模式；这种复杂的波形通过 FT 后会被分解为一系列正弦分量，背后的频率构成便一目了然。这一原理不仅是声学的基础，更是现代通信、图像处理及信号分析等领域的灵魂。</p>
<p>录音时的电平图像、示波器上显示的波形等，记录的都是物理量随时间流逝的状态。横坐标是时间，纵坐标是物理量的值。这种以时间为参考来衡量动态世界的方式被称为<strong>时域 (Time Domain)</strong> 分析。而通过傅立叶变换，得以从另一个完全不同的视角去审视信号——这就是<strong>频域 (Frequency Domain)</strong> 分析。前者好比整首曲子的录音，后者就是这首曲子的乐谱。</p>
<p>接下来的讲解来自于 <a href="https://youtu.be/spUNpyF58BY?si=koIXNLcFVXmvn_89" class="external-link" target="_blank">3Blue1Brown</a> 和 <a href="https://youtu.be/nmgFG7PUHfo?si=ZxoRL4f8_F1_nRN4" class="external-link" target="_blank">Veritasium</a> 的视频，我非常推荐去观看原视频。此处仅做总结，加上一部分自己的感悟。</p>
<p>从数学上讲，连续信号的傅里叶变换通过积分将时域函数 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">f(t)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mclose">)</span></span></span></span> 映射为频域函数 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>F</mi><mo stretchy="false">(</mo><mi>ω</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">F(\omega)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.1389em;">F</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.0359em;">ω</span><span class="mclose">)</span></span></span></span>：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>F</mi><mo stretchy="false">(</mo><mi>ω</mi><mo stretchy="false">)</mo><mo>=</mo><msubsup><mo>∫</mo><mrow><mo>−</mo><mi mathvariant="normal">∞</mi></mrow><mi mathvariant="normal">∞</mi></msubsup><mi>f</mi><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo><msup><mi>e</mi><mrow><mo>−</mo><mi>j</mi><mi>ω</mi><mi>t</mi></mrow></msup><mi mathvariant="normal">d</mi><mi>t</mi></mrow><annotation encoding="application/x-tex">F(\omega) = \int_{-\infty}^{\infty}f(t)e^{-j\omega t}\mathrm{d}t
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.1389em;">F</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.0359em;">ω</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.3846em;vertical-align:-0.9703em;"></span><span class="mop"><span class="mop op-symbol large-op" style="margin-right:0.4445em;position:relative;top:-0.0011em;">∫</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.4143em;"><span style="top:-1.7881em;margin-left:-0.4445em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">∞</span></span></span></span><span style="top:-3.8129em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">∞</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.9703em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mclose">)</span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8747em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight" style="margin-right:0.0572em;">j</span><span class="mord mathnormal mtight" style="margin-right:0.0359em;">ω</span><span class="mord mathnormal mtight">t</span></span></span></span></span></span></span></span></span><span class="mord mathrm">d</span><span class="mord mathnormal">t</span></span></span></span></span></p>
<p>这个公式乍一看很吓人，不妨拆开来看看。公式中的 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>j</mi></mrow><annotation encoding="application/x-tex">j</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.854em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.0572em;">j</span></span></span></span> 就是复数单位 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>i</mi></mrow><annotation encoding="application/x-tex">i</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6595em;"></span><span class="mord mathnormal">i</span></span></span></span>. 在复平面中，横坐标代表实部，纵坐标代表虚部。这就意味着当一个矢量乘以 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>i</mi></mrow><annotation encoding="application/x-tex">i</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6595em;"></span><span class="mord mathnormal">i</span></span></span></span> 后方向会和原矢量垂直。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/Complex_Plane.avif" alt="矢量 z1=2+3iz1=2+3iz1​=2+3i，乘以 iii 后得到新的矢量 z2=−3+2iz2=-3+2iz2​=−3+2i。z2z2z2​ 与 z1z1z1​ 的夹角正好是 90°." onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>矢量 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>z</mi><mn>1</mn></msub><mo>=</mo><mn>2</mn><mo>+</mo><mn>3</mn><mi>i</mi></mrow><annotation encoding="application/x-tex">z_1=2+3i</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.5806em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.044em;">z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.044em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7278em;vertical-align:-0.0833em;"></span><span class="mord">2</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6595em;"></span><span class="mord">3</span><span class="mord mathnormal">i</span></span></span></span>，乘以 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>i</mi></mrow><annotation encoding="application/x-tex">i</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6595em;"></span><span class="mord mathnormal">i</span></span></span></span> 后得到新的矢量 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>z</mi><mn>2</mn></msub><mo>=</mo><mo>−</mo><mn>3</mn><mo>+</mo><mn>2</mn><mi>i</mi></mrow><annotation encoding="application/x-tex">z_2=-3+2i</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.5806em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.044em;">z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.044em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7278em;vertical-align:-0.0833em;"></span><span class="mord">−</span><span class="mord">3</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6595em;"></span><span class="mord">2</span><span class="mord mathnormal">i</span></span></span></span>。<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>z</mi><mn>2</mn></msub></mrow><annotation encoding="application/x-tex">z_2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.5806em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.044em;">z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.044em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 与 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>z</mi><mn>1</mn></msub></mrow><annotation encoding="application/x-tex">z_1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.5806em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.044em;">z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.044em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 的夹角正好是 90°.</span>
		</figcaption>
	
</figure><p></p>
<p>假设某一函数反映了平面内某一点的位置随时间变化的关系：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mtext>d</mtext><mrow><mtext>d</mtext><mi>t</mi></mrow></mfrac><msup><mi>e</mi><mrow><mi>i</mi><mi>t</mi></mrow></msup><mo>=</mo><mi>i</mi><mo>⋅</mo><msup><mi>e</mi><mrow><mi>i</mi><mi>t</mi></mrow></msup></mrow><annotation encoding="application/x-tex">\frac{ \text{d} }{ \text{d}t }e^{it}=i\cdot e^{it}
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.0574em;vertical-align:-0.686em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3714em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">d</span></span><span class="mord mathnormal">t</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">d</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8747em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">i</span><span class="mord mathnormal mtight">t</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6595em;"></span><span class="mord mathnormal">i</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8747em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8747em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">i</span><span class="mord mathnormal mtight">t</span></span></span></span></span></span></span></span></span></span></span></span></span></p>
<p>把等式左侧看作速度矢量，右侧看作位置矢量。由于右侧比左侧多了一个旋转因子 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>i</mi></mrow><annotation encoding="application/x-tex">i</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6595em;"></span><span class="mord mathnormal">i</span></span></span></span>，速度矢量的方向将会永远垂直于位置矢量。随着时间 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>t</mi></mrow><annotation encoding="application/x-tex">t</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6151em;"></span><span class="mord mathnormal">t</span></span></span></span> 的增长，将在复平面上画出一个圆。这便引出了数学中最伟大的公式——<strong>欧拉公式 (Euler's Formula)</strong>：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msup><mi>e</mi><mrow><mi>i</mi><mi>t</mi></mrow></msup><mo>=</mo><mi>cos</mi><mo>⁡</mo><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo><mo>+</mo><mi>i</mi><mi>sin</mi><mo>⁡</mo><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">e^{it} = \cos(t)+i\sin(t)
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8747em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8747em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">i</span><span class="mord mathnormal mtight">t</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mop">cos</span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">i</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">sin</span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mclose">)</span></span></span></span></span></p>
<p>如果要改变绘制这个圆的速度，需引入频率参数 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>ω</mi></mrow><annotation encoding="application/x-tex">\omega</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.0359em;">ω</span></span></span></span> 改变速度矢量的虚部 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>ω</mi><mi>i</mi><mo>⋅</mo><mi>t</mi></mrow><annotation encoding="application/x-tex">\omega i\cdot t</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6595em;"></span><span class="mord mathnormal" style="margin-right:0.0359em;">ω</span><span class="mord mathnormal">i</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6151em;"></span><span class="mord mathnormal">t</span></span></span></span>。如果再为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>e</mi></mrow><annotation encoding="application/x-tex">e</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">e</span></span></span></span> 的幂加上实部变成 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mi>σ</mi><mo>+</mo><mi>ω</mi><mi>i</mi><mo stretchy="false">)</mo><mi>t</mi></mrow><annotation encoding="application/x-tex">(\sigma+\omega i)t</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.0359em;">σ</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.0359em;">ω</span><span class="mord mathnormal">i</span><span class="mclose">)</span><span class="mord mathnormal">t</span></span></span></span>，此时速度矢量在水平方向上也有了分量，随时间变化就能够绘制出螺旋线。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/Complex_Plane_2.avif" alt="绘制的轨迹随 eee 的幂的变化。aaa 是矢量的实部，决定着图像随时间的增益或衰减；bbb 是虚部，决定着图像的震荡速度。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>绘制的轨迹随 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>e</mi></mrow><annotation encoding="application/x-tex">e</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">e</span></span></span></span> 的幂的变化。<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>a</mi></mrow><annotation encoding="application/x-tex">a</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">a</span></span></span></span> 是矢量的实部，决定着图像随时间的增益或衰减；<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>b</mi></mrow><annotation encoding="application/x-tex">b</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">b</span></span></span></span> 是虚部，决定着图像的震荡速度。</span>
		</figcaption>
	
</figure><p></p>
<p>将旋转基底乘以原始信号函数 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">f(t)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mclose">)</span></span></span></span>，在视觉上这就产生了一种类似于将原信号「缠绕」在单位圆上的效果。在这个缠绕图像中，质心的位移正是频率提取的关键。从物理上看，质心的位置取决于图像分布的均衡程度。计算质心的方法是取若干个样本点，并计算它们位置的平均值。而当样本点的个数趋于无穷时，就变成了定积分。当缠绕频率与信号内在频率相同时，质心会猛然偏离原点，偏移距离和方向便反映了该频率的强度与相位。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/Complex_Plane_3.avif" alt="蓝色曲线为函数 gt=cos⁡2π⋅2t+1.5gt=\cos2\pi\cdot 2t+1.5gt=cos2π⋅2t+1.5 在单位圆上缠绕的效果。fff 是缠绕频率，红点是质心。观察质心轨迹可以发现，当 f=2f=2f=2（即等于 gtgtgt 的频率时）时，质心在 xxx 轴上的位移最大；而其他频率时仅在原点附近轻微振荡（忽略质心的初始位置，因为函数向上偏移了）。在偏移达到最大的同时，绕成的图像形成了帕斯卡蜗线。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>蓝色曲线为函数 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>g</mi><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo><mo>=</mo><mi>cos</mi><mo>⁡</mo><mo stretchy="false">(</mo><mn>2</mn><mi>π</mi><mo>⋅</mo><mn>2</mn><mi>t</mi><mo stretchy="false">)</mo><mo>+</mo><mn>1.5</mn></mrow><annotation encoding="application/x-tex">g(t)=\cos(2\pi\cdot 2t)+1.5</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.0359em;">g</span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mop">cos</span><span class="mopen">(</span><span class="mord">2</span><span class="mord mathnormal" style="margin-right:0.0359em;">π</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">2</span><span class="mord mathnormal">t</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1.5</span></span></span></span> 在单位圆上缠绕的效果。<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi></mrow><annotation encoding="application/x-tex">f</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span></span></span></span> 是缠绕频率，红点是质心。观察质心轨迹可以发现，当 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mo>=</mo><mn>2</mn></mrow><annotation encoding="application/x-tex">f=2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">2</span></span></span></span>（即等于 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>g</mi><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">g(t)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.0359em;">g</span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mclose">)</span></span></span></span> 的频率时）时，质心在 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi></mrow><annotation encoding="application/x-tex">x</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">x</span></span></span></span> 轴上的位移最大；而其他频率时仅在原点附近轻微振荡（忽略质心的初始位置，因为函数向上偏移了）。在偏移达到最大的同时，绕成的图像形成了帕斯卡蜗线。</span>
		</figcaption>
	
</figure><p></p>
<p>为了方便解释，故采用了质心这一思想。而傅里叶变换就不需要对图形的每个采样点取平均值。换个说法，最大位移就指向了原函数的频率。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/Complex_Plane_4.avif" alt="紫色的轨迹即质心随 fff 的变化在 xxx 轴上的偏移量。忽略初始的位置变化，峰值出现的位置正好等于 gtgtgt 的频率。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>紫色的轨迹即质心随 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi></mrow><annotation encoding="application/x-tex">f</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span></span></span></span> 的变化在 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi></mrow><annotation encoding="application/x-tex">x</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">x</span></span></span></span> 轴上的偏移量。忽略初始的位置变化，峰值出现的位置正好等于 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>g</mi><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">g(t)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.0359em;">g</span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mclose">)</span></span></span></span> 的频率。</span>
		</figcaption>
	
</figure><p></p>
<p>傅里叶变换要求信号必须是收敛的，即必须衰减得足够快，在 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>t</mi><mo>→</mo><mi mathvariant="normal">∞</mi></mrow><annotation encoding="application/x-tex">t\to\infty</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6151em;"></span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">→</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord">∞</span></span></span></span> 内能够收敛到 0 或一个常数上，以及能量是有限的。换句话说，信号是绝对可积的：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msubsup><mo>∫</mo><mrow><mo>−</mo><mi mathvariant="normal">∞</mi></mrow><mi mathvariant="normal">∞</mi></msubsup><mi mathvariant="normal">∣</mi><mi>f</mi><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo><mi mathvariant="normal">∣</mi><mi>d</mi><mi>t</mi><mo>&lt;</mo><mi mathvariant="normal">∞</mi></mrow><annotation encoding="application/x-tex">\int_{-\infty}^{\infty}|f(t)|dt &lt; \infty
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.3846em;vertical-align:-0.9703em;"></span><span class="mop"><span class="mop op-symbol large-op" style="margin-right:0.4445em;position:relative;top:-0.0011em;">∫</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.4143em;"><span style="top:-1.7881em;margin-left:-0.4445em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">∞</span></span></span></span><span style="top:-3.8129em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">∞</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.9703em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">∣</span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mclose">)</span><span class="mord">∣</span><span class="mord mathnormal">d</span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&lt;</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord">∞</span></span></span></span></span></p>
<p>以及平方可积的：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msubsup><mo>∫</mo><mrow><mo>−</mo><mi mathvariant="normal">∞</mi></mrow><mi mathvariant="normal">∞</mi></msubsup><mi mathvariant="normal">∣</mi><mi>f</mi><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo><msup><mi mathvariant="normal">∣</mi><mn>2</mn></msup><mi>d</mi><mi>t</mi><mo>&lt;</mo><mi mathvariant="normal">∞</mi></mrow><annotation encoding="application/x-tex">\int_{-\infty}^{\infty}|f(t)|^2dt &lt; \infty
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.3846em;vertical-align:-0.9703em;"></span><span class="mop"><span class="mop op-symbol large-op" style="margin-right:0.4445em;position:relative;top:-0.0011em;">∫</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.4143em;"><span style="top:-1.7881em;margin-left:-0.4445em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">∞</span></span></span></span><span style="top:-3.8129em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">∞</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.9703em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">∣</span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mclose">)</span><span class="mord"><span class="mord">∣</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span><span class="mord mathnormal">d</span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&lt;</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord">∞</span></span></span></span></span></p>
<p>而如果信号是永恒不变的，在尝试计算前文提到的质心的时候，积分的结果就会发散，从而无法反映出正确的结果。直流和阶跃是两种很重要的信号，然而它们并不满足绝对可积的条件。这时候就要引入一个衰减因子，也就是前文中提到的在 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>e</mi></mrow><annotation encoding="application/x-tex">e</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">e</span></span></span></span> 的幂中的实部 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>σ</mi></mrow><annotation encoding="application/x-tex">\sigma</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.0359em;">σ</span></span></span></span>，强行让信号收敛。而这就是<strong>拉普拉斯变换 (Laplace Transform)</strong>：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>F</mi><mo stretchy="false">(</mo><mi>s</mi><mo stretchy="false">)</mo><mo>=</mo><msubsup><mo>∫</mo><mn>0</mn><mi mathvariant="normal">∞</mi></msubsup><mi>f</mi><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo><msup><mi>e</mi><mrow><mo>−</mo><mi>s</mi><mi>t</mi></mrow></msup><mtext>d</mtext><mi>t</mi></mrow><annotation encoding="application/x-tex">F(s)=\int_{0}^{\infty}f(t)e^{-st}\text{d}t
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.1389em;">F</span><span class="mopen">(</span><span class="mord mathnormal">s</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.3262em;vertical-align:-0.9119em;"></span><span class="mop"><span class="mop op-symbol large-op" style="margin-right:0.4445em;position:relative;top:-0.0011em;">∫</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.4143em;"><span style="top:-1.7881em;margin-left:-0.4445em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">0</span></span></span></span><span style="top:-3.8129em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">∞</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.9119em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mclose">)</span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8436em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight">s</span><span class="mord mathnormal mtight">t</span></span></span></span></span></span></span></span></span><span class="mord text"><span class="mord">d</span></span><span class="mord mathnormal">t</span></span></span></span></span></p>
<p>其中，拉普拉斯算子 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi><mo>=</mo><mi>σ</mi><mo>+</mo><mi>j</mi><mi>ω</mi></mrow><annotation encoding="application/x-tex">s = \sigma + j\omega</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">s</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal" style="margin-right:0.0359em;">σ</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.854em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.0572em;">j</span><span class="mord mathnormal" style="margin-right:0.0359em;">ω</span></span></span></span>. FT 可以近似地看作 LT 在特定条件下的一种特殊情况。</p>
<p>在工程中通常不会对时间跨度这么长的信号进行分析，而是从中截取一段有限的时间区间 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">[</mo><mn>0</mn><mo separator="true">,</mo><mi>T</mi><mo stretchy="false">]</mo></mrow><annotation encoding="application/x-tex">[0, T]</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">[</span><span class="mord">0</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.1389em;">T</span><span class="mclose">]</span></span></span></span> 进行分析，就像只对屏幕上窗口内展示的信号进行分析，原信号 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">f(t)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mclose">)</span></span></span></span> 乘以一个<strong>窗函数 (Window Function)</strong>。在这段区间以外的信号都会强制变成 0.</p>
<p>然而，这种截断并非没有代价。如果截取的区间未能恰好包含信号频率的完整周期时，窗口的首尾两端就会发生剧烈的跳变。这种时域上的不连续性就会造成很多本不应该出现的频率分量，导致频谱能量从主频向四周漫延。这便是所谓的<strong>频谱泄露 (Spectral Leakage)</strong>。频率主峰附近会隆起许多本不属于信号的<strong>旁瓣 (Sidelobes)</strong>。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/Spectural_Leakage.avif" alt="被截断的信号。首尾处出现跳变。NI" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>被截断的信号。首尾处出现跳变。<a href="https://www.ni.com/docs/en-US/bundle/ni-scope/page/spectral-leakage.html?srsltid=AfmBOoq5OivBKvnMMr_DuP6qir8p-0K53IeUuNjueGeUjKG-izoBPIyl" class="external-link" target="_blank">NI</a></span>
		</figcaption>
	
</figure><p></p>
<p>为了抑制这种副作用，信号应该「淡入淡出」。通过使用加权窗，窗口中心区域的信号获得更高的权重，而让两侧边缘的信号平滑地衰减至零。这种柔和的过渡能够有效平复截断处的跳变，从而压制旁瓣能量。常见的窗函数如下，此处暂时不做过多解释：</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/Rectangular_Window.svg" alt="矩形窗 Rectangular Window。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>矩形窗 (Rectangular Window)。</span>
		</figcaption>
	
</figure><p></p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/Hann_Window.svg" alt="汉恩窗 Hann Window。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>汉恩窗 (Hann Window)。</span>
		</figcaption>
	
</figure><p></p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/Hamming_Window.svg" alt="汉明窗 Hamming Window. 与汉恩窗类似，窗口两端留有约 0.08 的台阶，不归零。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>汉明窗 (Hamming Window). 与汉恩窗类似，窗口两端留有约 0.08 的台阶，不归零。</span>
		</figcaption>
	
</figure><p></p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/Blackman_Window.svg" alt="布莱克曼窗 Blackman Window。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>布莱克曼窗 (Blackman Window)。</span>
		</figcaption>
	
</figure><p></p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/Triangular_Window.svg" alt="三角窗 Triangle Window。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>三角窗 (Triangle Window)。</span>
		</figcaption>
	
</figure><p></p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/Flat_Top_Window.svg" alt="平顶窗 Flat Window。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>平顶窗 (Flat Window)。</span>
		</figcaption>
	
</figure><p></p>
<p>读者可能已经注意到了，频率相同时的图形颇有意思，此处粗略介绍一下。假设信号是一个简谐波加上直流偏量：<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>g</mi><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo><mo>=</mo><mi>B</mi><mo>+</mo><mi>A</mi><mi>sin</mi><mo>⁡</mo><mo stretchy="false">(</mo><mn>2</mn><mi>π</mi><msub><mi>f</mi><mn>0</mn></msub><mi>t</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">g(t)=B+A\sin(2\pi f_0t)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.0359em;">g</span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal" style="margin-right:0.0502em;">B</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">A</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">sin</span><span class="mopen">(</span><span class="mord">2</span><span class="mord mathnormal" style="margin-right:0.0359em;">π</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord mathnormal">t</span><span class="mclose">)</span></span></span></span>. 缠绕的过程本质上是将时间 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>t</mi></mrow><annotation encoding="application/x-tex">t</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6151em;"></span><span class="mord mathnormal">t</span></span></span></span> 映射为极坐标中的角度 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>θ</mi></mrow><annotation encoding="application/x-tex">\theta</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.0278em;">θ</span></span></span></span>. 如果以频率 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi></mrow><annotation encoding="application/x-tex">f</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span></span></span></span> 进行旋转，那么在 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>t</mi></mrow><annotation encoding="application/x-tex">t</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6151em;"></span><span class="mord mathnormal">t</span></span></span></span> 时刻，旋转过的总角度（弧度）为：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>θ</mi><mo>=</mo><mn>2</mn><mi>π</mi><mi>f</mi><mi>t</mi></mrow><annotation encoding="application/x-tex">\theta = 2\pi ft
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.0278em;">θ</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord">2</span><span class="mord mathnormal" style="margin-right:0.0359em;">π</span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mord mathnormal">t</span></span></span></span></span></p>
<p>可得时间 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>t</mi></mrow><annotation encoding="application/x-tex">t</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6151em;"></span><span class="mord mathnormal">t</span></span></span></span> 与角度 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>θ</mi></mrow><annotation encoding="application/x-tex">\theta</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.0278em;">θ</span></span></span></span> 的关系：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>t</mi><mo>=</mo><mfrac><mi>θ</mi><mrow><mn>2</mn><mi>π</mi><mi>f</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">t=\frac{\theta}{2\pi f}
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6151em;"></span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.2519em;vertical-align:-0.8804em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3714em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span><span class="mord mathnormal" style="margin-right:0.0359em;">π</span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0278em;">θ</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.8804em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p>
<p>代入 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>g</mi><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">g(t)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.0359em;">g</span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mclose">)</span></span></span></span> 作为极径 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>r</mi></mrow><annotation encoding="application/x-tex">r</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.0278em;">r</span></span></span></span>：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mtable rowspacing="0.25em" columnalign="right left" columnspacing="0em"><mtr><mtd class="mtr-glue"></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mi>r</mi><mo stretchy="false">(</mo><mi>θ</mi><mo stretchy="false">)</mo></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><mi>g</mi><mrow><mo fence="true">(</mo><mfrac><mi>θ</mi><mrow><mn>2</mn><mi>π</mi><mi>f</mi></mrow></mfrac><mo fence="true">)</mo></mrow><mo>=</mo><mi>B</mi><mo>+</mo><mi>A</mi><mi>sin</mi><mo>⁡</mo><mrow><mo fence="true">(</mo><mn>2</mn><mi>π</mi><msub><mi>f</mi><mn>0</mn></msub><mo>⋅</mo><mfrac><mi>θ</mi><mrow><mn>2</mn><mi>π</mi><mi>f</mi></mrow></mfrac><mo fence="true">)</mo></mrow></mrow></mstyle></mtd><mtd class="mtr-glue"></mtd><mtd class="mml-eqn-num"></mtd></mtr><mtr><mtd class="mtr-glue"></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><mi>B</mi><mo>+</mo><mi>A</mi><mi>s</mi><mi>i</mi><mi>n</mi><mo stretchy="false">(</mo><mfrac><mi>f</mi><msub><mi>f</mi><mn>0</mn></msub></mfrac><mtext>​​</mtext><mi>θ</mi><mo stretchy="false">)</mo></mrow></mstyle></mtd><mtd class="mtr-glue"></mtd><mtd class="mml-eqn-num"></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{align}
r(\theta) &amp;= g\left( \frac{\theta}{2\pi f} \right)=B+A\sin\left( 2\pi f_0\cdot \frac{\theta}{2\pi f} \right) \\
&amp;= B+Asin(\frac{f}{f_0}​​\theta)
\end{align}
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:4.9519em;vertical-align:-2.226em;"></span><span class="mtable"><span class="col-align-r"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.726em;"><span style="top:-4.726em;"><span class="pstrut" style="height:3.45em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0278em;">r</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.0278em;">θ</span><span class="mclose">)</span></span></span><span style="top:-2.1045em;"><span class="pstrut" style="height:3.45em;"></span><span class="mord"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.226em;"><span></span></span></span></span></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.726em;"><span style="top:-4.726em;"><span class="pstrut" style="height:3.45em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord mathnormal" style="margin-right:0.0359em;">g</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">(</span></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3714em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span><span class="mord mathnormal" style="margin-right:0.0359em;">π</span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0278em;">θ</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.8804em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">)</span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord mathnormal" style="margin-right:0.0502em;">B</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord mathnormal">A</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">sin</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">(</span></span><span class="mord">2</span><span class="mord mathnormal" style="margin-right:0.0359em;">π</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3714em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span><span class="mord mathnormal" style="margin-right:0.0359em;">π</span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0278em;">θ</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.8804em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">)</span></span></span></span></span><span style="top:-2.1045em;"><span class="pstrut" style="height:3.45em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord mathnormal" style="margin-right:0.0502em;">B</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord mathnormal">A</span><span class="mord mathnormal">s</span><span class="mord mathnormal">in</span><span class="mopen">(</span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3714em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.8804em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mord">​​</span><span class="mord mathnormal" style="margin-right:0.0278em;">θ</span><span class="mclose">)</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.226em;"><span></span></span></span></span></span></span></span><span class="tag"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.726em;"><span style="top:-4.726em;"><span class="pstrut" style="height:3.45em;"></span><span class="eqn-num"></span></span><span style="top:-2.1045em;"><span class="pstrut" style="height:3.45em;"></span><span class="eqn-num"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.226em;"><span></span></span></span></span></span></span></span></span></p>
<p>而当 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mo>=</mo><msub><mi>f</mi><mn>0</mn></msub></mrow><annotation encoding="application/x-tex">f=f_0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 时就会变成：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>r</mi><mo stretchy="false">(</mo><mi>θ</mi><mo stretchy="false">)</mo><mo>=</mo><mi>B</mi><mo>+</mo><mi>A</mi><mi>s</mi><mi>i</mi><mi>n</mi><mo stretchy="false">(</mo><mi>θ</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">r(θ)=B+Asin(θ)
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.0278em;">r</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.0278em;">θ</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal" style="margin-right:0.0502em;">B</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">A</span><span class="mord mathnormal">s</span><span class="mord mathnormal">in</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.0278em;">θ</span><span class="mclose">)</span></span></span></span></span></p>
<p>这正是<strong>帕斯卡蜗线 (Limaçon of Pascal)</strong> 的标准方程。而当 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>B</mi><mo>=</mo><mi>A</mi></mrow><annotation encoding="application/x-tex">B=A</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.0502em;">B</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal">A</span></span></span></span> 时就是大名鼎鼎的<strong>心脏线 (Cardioid)</strong>。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/Cardioid.avif" alt="当 B=AB=AB=A 时，帕斯卡蜗线呈尖点型，即心脏线。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>当 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>B</mi><mo>=</mo><mi>A</mi></mrow><annotation encoding="application/x-tex">B=A</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.0502em;">B</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal">A</span></span></span></span> 时，帕斯卡蜗线呈尖点型，即心脏线。</span>
		</figcaption>
	
</figure><p></p>
<p>回到正题。现实生活中各种信号变化并非无限精细，同时仪器输出的波形看似光滑，也会存在不可分辨的最小单位。所以最终得到的是离散、有限的信号。此时连续傅里叶变换不再适用，取而代之的是<strong>离散傅里叶变换 (Discrete Fourier Transform, DFT)</strong>。对于长度为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi></mrow><annotation encoding="application/x-tex">N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.109em;">N</span></span></span></span> 的序列 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi><mo stretchy="false">[</mo><mi>n</mi><mo stretchy="false">]</mo></mrow><annotation encoding="application/x-tex">x[n]</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">x</span><span class="mopen">[</span><span class="mord mathnormal">n</span><span class="mclose">]</span></span></span></span>，对其 DFT 的公式为：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>X</mi><mo stretchy="false">[</mo><mi>k</mi><mo stretchy="false">]</mo><mo>=</mo><munderover><mo>∑</mo><mrow><mi>n</mi><mo>=</mo><mn>0</mn></mrow><mrow><mi>N</mi><mo>−</mo><mn>1</mn></mrow></munderover><mi>x</mi><mo stretchy="false">[</mo><mi>n</mi><mo stretchy="false">]</mo><mo>⋅</mo><msup><mi>e</mi><mrow><mo>−</mo><mi>j</mi><mfrac><mrow><mn>2</mn><mi>π</mi></mrow><mi>N</mi></mfrac><mi>n</mi><mi>k</mi></mrow></msup><mo separator="true">,</mo><mi>k</mi><mo>=</mo><mn>0</mn><mo separator="true">,</mo><mn>1</mn><mo separator="true">,</mo><mn>2</mn><mo separator="true">,</mo><mo>…</mo><mo separator="true">,</mo><mi>N</mi><mo>−</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">X[k] = \sum_{n=0}^{N-1}x[n]\cdot e^{-j\frac{2\pi}{N}nk}, k=0, 1, 2, \dots, N-1
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.0785em;">X</span><span class="mopen">[</span><span class="mord mathnormal" style="margin-right:0.0315em;">k</span><span class="mclose">]</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:3.0954em;vertical-align:-1.2671em;"></span><span class="mop op-limits"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.8283em;"><span style="top:-1.8829em;margin-left:0em;"><span class="pstrut" style="height:3.05em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">n</span><span class="mrel mtight">=</span><span class="mord mtight">0</span></span></span></span><span style="top:-3.05em;"><span class="pstrut" style="height:3.05em;"></span><span><span class="mop op-symbol large-op">∑</span></span></span><span style="top:-4.3em;margin-left:0em;"><span class="pstrut" style="height:3.05em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.109em;">N</span><span class="mbin mtight">−</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.2671em;"><span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span><span class="mopen">[</span><span class="mord mathnormal">n</span><span class="mclose">]</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1.1985em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:1.004em;"><span style="top:-3.413em;margin-right:0.05em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight" style="margin-right:0.0572em;">j</span><span class="mord mtight"><span class="mopen nulldelimiter sizing reset-size3 size6"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8443em;"><span style="top:-2.656em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.109em;">N</span></span></span></span><span style="top:-3.2255em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line mtight" style="border-bottom-width:0.049em;"></span></span><span style="top:-3.384em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mtight"><span class="mord mtight">2</span><span class="mord mathnormal mtight" style="margin-right:0.0359em;">π</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.344em;"><span></span></span></span></span></span><span class="mclose nulldelimiter sizing reset-size3 size6"></span></span><span class="mord mathnormal mtight" style="margin-right:0.0315em;">nk</span></span></span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.0315em;">k</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8778em;vertical-align:-0.1944em;"></span><span class="mord">0</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">1</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">2</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner">…</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.109em;">N</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1</span></span></span></span></span></p>
<p>离散的视角下，圆周不能平滑地旋转，而是像那种一次走一格的时钟。在 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>e</mi></mrow><annotation encoding="application/x-tex">e</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">e</span></span></span></span> 的幂中，如果有 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi></mrow><annotation encoding="application/x-tex">N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.109em;">N</span></span></span></span> 个采样点，那么就需要将圆弧分成 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi></mrow><annotation encoding="application/x-tex">N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.109em;">N</span></span></span></span> 份，也就是 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mstyle scriptlevel="0" displaystyle="false"><mfrac><mrow><mn>2</mn><mi>π</mi></mrow><mi>N</mi></mfrac></mstyle></mrow><annotation encoding="application/x-tex">\tfrac{2\pi}{N}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.1901em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8451em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.109em;">N</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span><span class="mord mathnormal mtight" style="margin-right:0.0359em;">π</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span>. 而 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>n</mi></mrow><annotation encoding="application/x-tex">n</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">n</span></span></span></span> 就表示当前是第几个采样点。<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.0315em;">k</span></span></span></span> 就是缠绕频率。</p>
<p>DFT 可以看作一个矩阵乘法 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>y</mi><mo>=</mo><mi>W</mi><mi>x</mi></mrow><annotation encoding="application/x-tex">y=Wx</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.0359em;">y</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.1389em;">W</span><span class="mord mathnormal">x</span></span></span></span>，其中 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>W</mi></mrow><annotation encoding="application/x-tex">W</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.1389em;">W</span></span></span></span> 是 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>n</mi><mo>×</mo><mi>n</mi></mrow><annotation encoding="application/x-tex">n\times n</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal">n</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">n</span></span></span></span> 的范德蒙德矩阵：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>X</mi><mo stretchy="false">[</mo><mn>0</mn><mo stretchy="false">]</mo></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>X</mi><mo stretchy="false">[</mo><mn>1</mn><mo stretchy="false">]</mo></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi mathvariant="normal">⋮</mi><mpadded height="0em" voffset="0em"><mspace mathbackground="black" width="0em" height="1.5em"></mspace></mpadded></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>X</mi><mo stretchy="false">[</mo><mi>n</mi><mo>−</mo><mn>1</mn><mo stretchy="false">]</mo></mrow></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>ω</mi><mn>0</mn></msup></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>ω</mi><mn>0</mn></msup></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mo lspace="0em" rspace="0em">…</mo></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>ω</mi><mn>0</mn></msup></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>ω</mi><mn>0</mn></msup></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>ω</mi><mn>1</mn></msup></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mo lspace="0em" rspace="0em">…</mo></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>ω</mi><mrow><mi>n</mi><mo>−</mo><mn>1</mn></mrow></msup></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi mathvariant="normal">⋮</mi><mpadded height="0em" voffset="0em"><mspace mathbackground="black" width="0em" height="1.5em"></mspace></mpadded></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi mathvariant="normal">⋮</mi><mpadded height="0em" voffset="0em"><mspace mathbackground="black" width="0em" height="1.5em"></mspace></mpadded></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mo lspace="0em" rspace="0em">⋱</mo></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi mathvariant="normal">⋮</mi><mpadded height="0em" voffset="0em"><mspace mathbackground="black" width="0em" height="1.5em"></mspace></mpadded></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>ω</mi><mn>0</mn></msup></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>ω</mi><mrow><mi>n</mi><mo>−</mo><mn>1</mn></mrow></msup></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mo lspace="0em" rspace="0em">…</mo></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>ω</mi><mrow><mo stretchy="false">(</mo><mi>n</mi><mo>−</mo><mn>1</mn><mo stretchy="false">)</mo><mo stretchy="false">(</mo><mi>n</mi><mo>−</mo><mn>1</mn><mo stretchy="false">)</mo></mrow></msup></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>x</mi><mo stretchy="false">[</mo><mn>0</mn><mo stretchy="false">]</mo></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>x</mi><mo stretchy="false">[</mo><mn>1</mn><mo stretchy="false">]</mo></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi mathvariant="normal">⋮</mi><mpadded height="0em" voffset="0em"><mspace mathbackground="black" width="0em" height="1.5em"></mspace></mpadded></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>x</mi><mo stretchy="false">[</mo><mi>n</mi><mo>−</mo><mn>1</mn><mo stretchy="false">]</mo></mrow></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\begin{bmatrix} 
X[0] \\ X[1] \\ \vdots \\ X[n-1] 
\end{bmatrix} = 
\begin{bmatrix} 
\omega^0 &amp; \omega^0 &amp; \dots &amp; \omega^0 \\
\omega^0 &amp; \omega^1 &amp; \dots &amp; \omega^{n-1} \\
\vdots &amp; \vdots &amp; \ddots &amp; \vdots \\
\omega^0 &amp; \omega^{n-1} &amp; \dots &amp; \omega^{(n-1)(n-1)}
\end{bmatrix}
\begin{bmatrix} 
x[0] \\ x[1] \\ \vdots \\ x[n-1] 
\end{bmatrix}
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:5.46em;vertical-align:-2.48em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.95em;"><span style="top:-4.95em;"><span class="pstrut" style="height:7.4em;"></span><span style="width:0.667em;height:5.4em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="5.4em" viewBox="0 0 667 5400"><path d="M403 1759 V84 H666 V0 H319 V1759 v1800 v1759 v84 h347 v-84
H403z M403 1759 V0 H319 V1759 v1800 v1759 v84 h84z"></path></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.45em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.98em;"><span style="top:-5.8275em;"><span class="pstrut" style="height:3.6875em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0785em;">X</span><span class="mopen">[</span><span class="mord">0</span><span class="mclose">]</span></span></span><span style="top:-4.6275em;"><span class="pstrut" style="height:3.6875em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0785em;">X</span><span class="mopen">[</span><span class="mord">1</span><span class="mclose">]</span></span></span><span style="top:-2.7675em;"><span class="pstrut" style="height:3.6875em;"></span><span class="mord"><span class="mord"><span class="mord">⋮</span><span class="mord rule" style="border-right-width:0em;border-top-width:1.5em;bottom:0em;"></span></span></span></span><span style="top:-1.5675em;"><span class="pstrut" style="height:3.6875em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0785em;">X</span><span class="mopen">[</span><span class="mord mathnormal">n</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord">1</span><span class="mclose">]</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.48em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.95em;"><span style="top:-4.95em;"><span class="pstrut" style="height:7.4em;"></span><span style="width:0.667em;height:5.4em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="5.4em" viewBox="0 0 667 5400"><path d="M347 1759 V0 H0 V84 H263 V1759 v1800 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v1800 v1759 h84z"></path></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.45em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:5.508em;vertical-align:-2.504em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.95em;"><span style="top:-4.95em;"><span class="pstrut" style="height:7.4em;"></span><span style="width:0.667em;height:5.4em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="5.4em" viewBox="0 0 667 5400"><path d="M403 1759 V84 H666 V0 H319 V1759 v1800 v1759 v84 h347 v-84
H403z M403 1759 V0 H319 V1759 v1800 v1759 v84 h84z"></path></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.45em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.004em;"><span style="top:-5.8515em;"><span class="pstrut" style="height:3.6875em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.0359em;">ω</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span></span></span></span></span></span></span><span style="top:-4.6515em;"><span class="pstrut" style="height:3.6875em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.0359em;">ω</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span></span></span></span></span></span></span><span style="top:-2.7915em;"><span class="pstrut" style="height:3.6875em;"></span><span class="mord"><span class="mord"><span class="mord">⋮</span><span class="mord rule" style="border-right-width:0em;border-top-width:1.5em;bottom:0em;"></span></span></span></span><span style="top:-1.5435em;"><span class="pstrut" style="height:3.6875em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.0359em;">ω</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.504em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.004em;"><span style="top:-5.8515em;"><span class="pstrut" style="height:3.6875em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.0359em;">ω</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span></span></span></span></span></span></span><span style="top:-4.6515em;"><span class="pstrut" style="height:3.6875em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.0359em;">ω</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span></span></span></span></span></span></span><span style="top:-2.7915em;"><span class="pstrut" style="height:3.6875em;"></span><span class="mord"><span class="mord"><span class="mord">⋮</span><span class="mord rule" style="border-right-width:0em;border-top-width:1.5em;bottom:0em;"></span></span></span></span><span style="top:-1.5435em;"><span class="pstrut" style="height:3.6875em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.0359em;">ω</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">n</span><span class="mbin mtight">−</span><span class="mord mtight">1</span></span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.504em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.004em;"><span style="top:-5.664em;"><span class="pstrut" style="height:3.5em;"></span><span class="mord"><span class="minner">…</span></span></span><span style="top:-4.464em;"><span class="pstrut" style="height:3.5em;"></span><span class="mord"><span class="minner">…</span></span></span><span style="top:-2.604em;"><span class="pstrut" style="height:3.5em;"></span><span class="mord"><span class="minner">⋱</span></span></span><span style="top:-1.356em;"><span class="pstrut" style="height:3.5em;"></span><span class="mord"><span class="minner">…</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.504em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.004em;"><span style="top:-5.8515em;"><span class="pstrut" style="height:3.6875em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.0359em;">ω</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span></span></span></span></span></span></span><span style="top:-4.6515em;"><span class="pstrut" style="height:3.6875em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.0359em;">ω</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">n</span><span class="mbin mtight">−</span><span class="mord mtight">1</span></span></span></span></span></span></span></span></span></span></span><span style="top:-2.7915em;"><span class="pstrut" style="height:3.6875em;"></span><span class="mord"><span class="mord"><span class="mord">⋮</span><span class="mord rule" style="border-right-width:0em;border-top-width:1.5em;bottom:0em;"></span></span></span></span><span style="top:-1.5435em;"><span class="pstrut" style="height:3.6875em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.0359em;">ω</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.888em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mopen mtight">(</span><span class="mord mathnormal mtight">n</span><span class="mbin mtight">−</span><span class="mord mtight">1</span><span class="mclose mtight">)</span><span class="mopen mtight">(</span><span class="mord mathnormal mtight">n</span><span class="mbin mtight">−</span><span class="mord mtight">1</span><span class="mclose mtight">)</span></span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.504em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.95em;"><span style="top:-4.95em;"><span class="pstrut" style="height:7.4em;"></span><span style="width:0.667em;height:5.4em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="5.4em" viewBox="0 0 667 5400"><path d="M347 1759 V0 H0 V84 H263 V1759 v1800 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v1800 v1759 h84z"></path></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.45em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.95em;"><span style="top:-4.95em;"><span class="pstrut" style="height:7.4em;"></span><span style="width:0.667em;height:5.4em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="5.4em" viewBox="0 0 667 5400"><path d="M403 1759 V84 H666 V0 H319 V1759 v1800 v1759 v84 h347 v-84
H403z M403 1759 V0 H319 V1759 v1800 v1759 v84 h84z"></path></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.45em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.98em;"><span style="top:-5.8275em;"><span class="pstrut" style="height:3.6875em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="mopen">[</span><span class="mord">0</span><span class="mclose">]</span></span></span><span style="top:-4.6275em;"><span class="pstrut" style="height:3.6875em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="mopen">[</span><span class="mord">1</span><span class="mclose">]</span></span></span><span style="top:-2.7675em;"><span class="pstrut" style="height:3.6875em;"></span><span class="mord"><span class="mord"><span class="mord">⋮</span><span class="mord rule" style="border-right-width:0em;border-top-width:1.5em;bottom:0em;"></span></span></span></span><span style="top:-1.5675em;"><span class="pstrut" style="height:3.6875em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="mopen">[</span><span class="mord mathnormal">n</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord">1</span><span class="mclose">]</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.48em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.95em;"><span style="top:-4.95em;"><span class="pstrut" style="height:7.4em;"></span><span style="width:0.667em;height:5.4em;"><svg xmlns="http://www.w3.org/2000/svg" width="0.667em" height="5.4em" viewBox="0 0 667 5400"><path d="M347 1759 V0 H0 V84 H263 V1759 v1800 v1759 H0 v84 H347z
M347 1759 V0 H263 V1759 v1800 v1759 h84z"></path></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.45em;"><span></span></span></span></span></span></span></span></span></span></span></span></p>
<p>为了得到单个点 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>X</mi><mo stretchy="false">[</mo><mi>k</mi><mo stretchy="false">]</mo></mrow><annotation encoding="application/x-tex">X[k]</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.0785em;">X</span><span class="mopen">[</span><span class="mord mathnormal" style="margin-right:0.0315em;">k</span><span class="mclose">]</span></span></span></span>，需要从 0 到 N-1 进行求和，其中涉及到 N 次复数乘法与 N-1 次复数加法，复杂度就是 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><mi>n</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(n)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.0278em;">O</span><span class="mopen">(</span><span class="mord mathnormal">n</span><span class="mclose">)</span></span></span></span>；而整个序列中含有 N 个点，需要将上述过程重复 N 次，总复杂度就是 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><msup><mi>N</mi><mn>2</mn></msup><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(N^2)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.0641em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.0278em;">O</span><span class="mopen">(</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.109em;">N</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span><span class="mclose">)</span></span></span></span>. 对于高采样率的信号计算量会变得非常巨大，无法实时分析。</p>
<p><strong>快速傅里叶变换 (Fast Fourier Transform, FFT)</strong> 则优化了算法的复杂度。这个算法曾经被数学家吉尔伯特·斯特朗描述为「我们一生中最重要的数值算法」。库利-图基算法是最常见的 FFT 算法。除此之外还有质因子算法 (Prime-Factor Algorithm) 等等，此处按下不表。</p>
<p>库利-图基算法靠两个手段优化。第一个是旋转因子的周期性与对称性。由于旋转因子 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msubsup><mi>W</mi><mi>N</mi><mrow><mi>n</mi><mi>k</mi></mrow></msubsup><mo>=</mo><msup><mi>e</mi><mrow><mo>−</mo><mi>j</mi><mfrac><mrow><mn>2</mn><mi>π</mi></mrow><mi>N</mi></mfrac><mi>n</mi><mi>k</mi></mrow></msup></mrow><annotation encoding="application/x-tex">W^{nk}_N = e^{-j\frac{2\pi}{N}nk}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.1244em;vertical-align:-0.2753em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1389em;">W</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8491em;"><span style="top:-2.4247em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.109em;">N</span></span></span><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.0315em;">nk</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2753em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.954em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.954em;"><span style="top:-3.363em;margin-right:0.05em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight" style="margin-right:0.0572em;">j</span><span class="mord mtight"><span class="mopen nulldelimiter sizing reset-size3 size6"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8443em;"><span style="top:-2.656em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.109em;">N</span></span></span></span><span style="top:-3.2255em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line mtight" style="border-bottom-width:0.049em;"></span></span><span style="top:-3.384em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mtight"><span class="mord mtight">2</span><span class="mord mathnormal mtight" style="margin-right:0.0359em;">π</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.344em;"><span></span></span></span></span></span><span class="mclose nulldelimiter sizing reset-size3 size6"></span></span><span class="mord mathnormal mtight" style="margin-right:0.0315em;">nk</span></span></span></span></span></span></span></span></span></span></span></span> 本质上就是圆上的点，因此具备两个特性：对称性 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msubsup><mi>W</mi><mi>N</mi><mrow><mi>k</mi><mo>+</mo><mi>N</mi><mi mathvariant="normal">/</mi><mn>2</mn></mrow></msubsup><mo>=</mo><mo>−</mo><msubsup><mi>W</mi><mi>N</mi><mi>k</mi></msubsup></mrow><annotation encoding="application/x-tex">W^{k+N/2}_{N} = -W^{k}_{N}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.3383em;vertical-align:-0.2935em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1389em;">W</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.0448em;"><span style="top:-2.4065em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.109em;">N</span></span></span></span><span style="top:-3.2198em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.0315em;">k</span><span class="mbin mtight">+</span><span class="mord mathnormal mtight" style="margin-right:0.109em;">N</span><span class="mord mtight">/2</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2935em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.1244em;vertical-align:-0.2753em;"></span><span class="mord">−</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1389em;">W</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8491em;"><span style="top:-2.4247em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.109em;">N</span></span></span></span><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.0315em;">k</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2753em;"><span></span></span></span></span></span></span></span></span></span> 和周期性 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msubsup><mi>W</mi><mi>N</mi><mrow><mi>k</mi><mo>+</mo><mi>N</mi></mrow></msubsup><mo>=</mo><msubsup><mi>W</mi><mi>N</mi><mi>k</mi></msubsup></mrow><annotation encoding="application/x-tex">W^{k+N}_{N} = W^k_N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.1828em;vertical-align:-0.2935em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1389em;">W</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8892em;"><span style="top:-2.4065em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.109em;">N</span></span></span></span><span style="top:-3.1031em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.0315em;">k</span><span class="mbin mtight">+</span><span class="mord mathnormal mtight" style="margin-right:0.109em;">N</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2935em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.1244em;vertical-align:-0.2753em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1389em;">W</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8491em;"><span style="top:-2.4247em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.109em;">N</span></span></span><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.0315em;">k</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2753em;"><span></span></span></span></span></span></span></span></span></span>. 这意味着很多复杂的乘法运算结果其实是一样的，或者只需变个符号。</p>
<p>而第二个是递归拆分。将一个长度为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi></mrow><annotation encoding="application/x-tex">N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.109em;">N</span></span></span></span> 的大问题，拆分成两个长度为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mi mathvariant="normal">/</mi><mn>2</mn></mrow><annotation encoding="application/x-tex">N/2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.109em;">N</span><span class="mord">/2</span></span></span></span> 的小问题。对于长度为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi></mrow><annotation encoding="application/x-tex">N</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.109em;">N</span></span></span></span> 的序列 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi><mo stretchy="false">[</mo><mi>n</mi><mo stretchy="false">]</mo></mrow><annotation encoding="application/x-tex">x[n]</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">x</span><span class="mopen">[</span><span class="mord mathnormal">n</span><span class="mclose">]</span></span></span></span>，分成偶数索引 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>x</mi><mrow><mn>2</mn><mi>m</mi></mrow></msub><mo>=</mo><msub><mi>x</mi><mn>0</mn></msub><mo separator="true">,</mo><msub><mi>x</mi><mn>2</mn></msub><mo separator="true">,</mo><mo>…</mo><mo separator="true">,</mo><msub><mi>x</mi><mrow><mi>N</mi><mo>−</mo><mn>2</mn></mrow></msub></mrow><annotation encoding="application/x-tex">x_{2m} = x_0, x_2, \dots, x_{N-2}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.5806em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span><span class="mord mathnormal mtight">m</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6389em;vertical-align:-0.2083em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner">…</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.109em;">N</span><span class="mbin mtight">−</span><span class="mord mtight">2</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2083em;"><span></span></span></span></span></span></span></span></span></span> 和奇数索引 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>x</mi><mrow><mn>2</mn><mi>m</mi><mo>+</mo><mn>1</mn></mrow></msub><mo>=</mo><msub><mi>x</mi><mn>1</mn></msub><mo separator="true">,</mo><msub><mi>x</mi><mn>3</mn></msub><mo separator="true">,</mo><mo>…</mo><mo separator="true">,</mo><msub><mi>x</mi><mrow><mi>N</mi><mo>−</mo><mn>1</mn></mrow></msub></mrow><annotation encoding="application/x-tex">x_{2m+1} = x_1, x_3, \dots, x_{N-1}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6389em;vertical-align:-0.2083em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span><span class="mord mathnormal mtight">m</span><span class="mbin mtight">+</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2083em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6389em;vertical-align:-0.2083em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">3</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner">…</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.109em;">N</span><span class="mbin mtight">−</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2083em;"><span></span></span></span></span></span></span></span></span></span> 两组。分别计算这两组的 DFT。 利用旋转因子的特性，将这两个小结果组合成最终的结果。这个过程会一直递归下去，直到拆成只有 2 个点的小单元。此时算法的复杂度就降到了 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><mi>N</mi><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mi>N</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(N \log_2 N)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.0278em;">O</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.109em;">N</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop"><span class="mop">lo<span style="margin-right:0.0139em;">g</span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.207em;"><span style="top:-2.4559em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2441em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.109em;">N</span><span class="mclose">)</span></span></span></span>.</p>
<p>得益于 FFT 的高效，它不仅统治了频率分析领域，更成为了数字信号处理的核心底座。例如，计算机中极为耗时的<strong>卷积 (Convolution)</strong> 运算，在频域下只需进行简单的乘法即可完成。这种运算在图像、音频处理，以及深度学习（例如卷积神经网络）等领域都有大量运用，以后有机会深入研究。</p>
<h2 id="滤波器" tabindex="-1">滤波器</h2>
<p>前面的频谱分析结果揭示了方波在高频处有频率分量的分布，而这也是导致听感刺耳的「罪魁祸首」。因此，需要将这些高频的成分过滤掉，实现这个功能的电路就被称为<strong>滤波器 (Filter)</strong>。滤波器有很多种，如果根据频率响应来划分，通低频、阻高频的就是<strong>低通滤波器 (Low-pass Filter, LPF)</strong>，反之就是<strong>高通滤波器 (High-pass Filter, HPF)</strong>；还有针对特定频段的<strong>带通 (Band-pass Filter, BPF)</strong> 和<strong>带阻 (Band-stop Filter, BSF)</strong> 滤波器。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/Type_Of_Filters.avif" alt="滤波器的主要类型。从上到下，从左至右依次为低通、高通、带通、带阻滤波器。图像的横坐标为频率，纵坐标为信号的强度或功率。All About Circuit" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>滤波器的主要类型。从上到下，从左至右依次为低通、高通、带通、带阻滤波器。图像的横坐标为频率，纵坐标为信号的强度或功率。<a href="https://www.allaboutcircuits.com/video-tutorials/op-amp-applications-band-pass-and-band-reject-active-filters/" class="external-link" target="_blank">All About Circuit</a></span>
		</figcaption>
	
</figure><p></p>
<p>除此之外，有些滤波器结构比较简单，可能只包含电阻、电容或电感这几种器件，仅起滤波作用，这类就被称为无源滤波器；而有的含有运算放大器、晶体管等元件，除了滤波还可以对信号提供增益，这类就被称为有源滤波器。在本篇文章中，将着重介绍最简单的 RC 无源低通滤波器。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/RC_Filter.avif" alt="最基本的 RC 低通滤波器电路。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>最基本的 RC 低通滤波器电路。</span>
		</figcaption>
	
</figure><p></p>
<p>这个电路的结构非常简单。如图所示，将一个电阻 R1 与信号输出串联，电容器 C1 与负载 RL 并联，就可以产生 RC 低通响应。电容器本质上就是由两块导体板与中间的绝缘介质（电介质）构成的。在直流电路中，当电容器接入电路中时，电极的电压会因为电子的聚集而不断上升，直到等于电源电压，电流停止流动，所以此时表现可近似看作「断路」。换句话说，对电流的<strong>阻抗 (Impedance)</strong>，或<strong>容抗 (Capacitive reactance)</strong> 很大。</p>
<p>而在交流电路中，由于电流方向不断反转，电压方向正向时，电容板充电；当电压方向反向时，电容板放电，并向相反方向充电。电流并没有实际流过绝缘介质，但电流在电路中不断地来回流动，就像在电路中「通过」了一样；而频率越高，电容器充放电的速度也越快，内部介质来不及建立稳态电压，电流在外部电路中来回流动的速率也越高。从电路外部来看，这等效于电容器的阻抗越小。总结来说，电容的一个重要性质就是「通高频，阻低频」。容抗的计算公式如下，也可以看出其大小与频率成反比：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>W</mi><msub><mi>X</mi><mi>C</mi></msub><mo>=</mo><mfrac><mn>1</mn><mrow><mn>2</mn><mi>π</mi><mi>f</mi><mi>C</mi></mrow></mfrac><mo>=</mo><mfrac><mn>1</mn><mrow><mi>ω</mi><mi>C</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">W
X_C = \frac{1}{2\pi fC}=\frac{1}{\omega C}
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord mathnormal" style="margin-right:0.1389em;">W</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0785em;">X</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.0785em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.0715em;">C</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.2019em;vertical-align:-0.8804em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3214em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span><span class="mord mathnormal" style="margin-right:0.0359em;">π</span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mord mathnormal" style="margin-right:0.0715em;">C</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.8804em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.0074em;vertical-align:-0.686em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3214em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0359em;">ω</span><span class="mord mathnormal" style="margin-right:0.0715em;">C</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p>
<p>当输入低频信号时，容抗相对于电阻的阻抗更高，因此电容上的分压降低；当输入高频信号时，容抗相对于电阻的较低，这意味着电阻上的分压降低，这样就较少的电压传输到负载。因此，低频通高频阻。而反过来，如果想要实现高通滤波，R1 和 C1 的位置就需要对调一下。这样，低频分量就会更多地分压在 C1 上，而高频分量更多地分压在 R1 上。</p>
<p>额外补充一点，除了电容器以外，<strong>电感器 (Inductor)</strong> 也有着与之相似的电气特性，只是两者恰好相反，即「通低频阻高频」。从物理本质上看，电感器通常由导线绕制成的线圈构成。当电流流经线圈时，会在其周围激发出磁场。如果电流 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>i</mi></mrow><annotation encoding="application/x-tex">i</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6595em;"></span><span class="mord mathnormal">i</span></span></span></span> 的大小或方向发生变化，穿过线圈的磁通量 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="normal">Φ</mi></mrow><annotation encoding="application/x-tex">\Phi</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord">Φ</span></span></span></span> 也会随之改变。根据<strong>法拉第电磁感应定律 (Faraday's Law of Induction)</strong>，当线圈不动而磁场变化时，穿过回路的磁通量也发生变化，由此在回路中激发的感生电动势 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="script">E</mi></mrow><annotation encoding="application/x-tex">\mathscr{E}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7em;"></span><span class="mord mathscr" style="margin-right:0.1858em;">E</span></span></span></span> 的大小与穿过线圈的磁通量变化率成正比。其数学表达式为：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi mathvariant="script">E</mi><mo>=</mo><mo>−</mo><mi>n</mi><mfrac><mrow><mi mathvariant="normal">d</mi><mi mathvariant="normal">Φ</mi></mrow><mrow><mi mathvariant="normal">d</mi><mi>t</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">\mathscr{E} = -n\frac{\mathrm{d}\Phi}{\mathrm{d}t}
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7em;"></span><span class="mord mathscr" style="margin-right:0.1858em;">E</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.0574em;vertical-align:-0.686em;"></span><span class="mord">−</span><span class="mord mathnormal">n</span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3714em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathrm">d</span><span class="mord mathnormal">t</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathrm">d</span><span class="mord">Φ</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p>
<p>其中，<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>n</mi></mrow><annotation encoding="application/x-tex">n</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">n</span></span></span></span> 是线圈的匝数。公式中的负号就是<strong>楞次定理 (Lenz's Law)</strong> 的体现，它表明感生电动势的方向始终倾向于阻碍原磁通量的变化。这种性质在电路中表现为对交流信号的阻碍作用，称为<strong>感抗 (Inductive Reactance)</strong>。感抗的计算公式如下：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>X</mi><mi>L</mi></msub><mo>=</mo><mn>2</mn><mi>π</mi><mi>f</mi><mi>L</mi><mo>=</mo><mi>ω</mi><mi>L</mi></mrow><annotation encoding="application/x-tex">X_L = 2\pi fL=\omega L
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0785em;">X</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.0785em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">L</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord">2</span><span class="mord mathnormal" style="margin-right:0.0359em;">π</span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mord mathnormal">L</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.0359em;">ω</span><span class="mord mathnormal">L</span></span></span></span></span></p>
<p>可以看出，感抗的大小与信号频率 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi></mrow><annotation encoding="application/x-tex">f</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span></span></span></span> 成正比。在低频或直流（<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mo>=</mo><mn>0</mn></mrow><annotation encoding="application/x-tex">f=0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">0</span></span></span></span>）情况下，感抗极小，电感可近似看作导线；而在高频情况下，感抗显著增大，从而实现对高频噪声的有效滤除。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/RL_filter.avif" alt="RL 滤波器。左侧为低通滤波器，右侧为高通滤波器。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>RL 滤波器。左侧为低通滤波器，右侧为高通滤波器。</span>
		</figcaption>
	
</figure><p></p>
<p>回到正题。「高频」和「低频」是个相当宽泛的概念。要落地到电路设计，必须计算出具体数值。滤波器不会引起显著衰减的频率范围称为<strong>通带</strong>，反之则称为<strong>阻带</strong>。模拟滤波器（如 RC 低通滤波器）在滤波并不会像一堵墙一样瞬间把信号切断，而是像一个陡坡，让信号强度从通带逐渐衰减到阻带。这意味着我们很难在曲线上找到一个「绝对」的阻塞起点。在工程实践中，通常将信号强度衰减至输入端 -3 dB 处的频率定义为<strong>截止频率 (Cutoff Frequency)</strong>。这里使用<strong>分贝 (Decibel, dB)</strong> 来定量描述信号功率的损耗，这种对数单位在衡量系统增益或衰减时比线性数值更加直观。为什么偏偏是 -3 dB 呢？由这个单位的计算方法就能得出：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>L</mi><mrow><mi mathvariant="normal">d</mi><mi mathvariant="normal">B</mi></mrow></msub><mo>=</mo><mn>10</mn><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>10</mn></msub><mrow><mo fence="true">(</mo><mfrac><msub><mi>P</mi><mn>1</mn></msub><msub><mi>P</mi><mn>0</mn></msub></mfrac><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">L_{\mathrm{dB}} = 10 \log_{10} {\left( \frac{P_1}{P_0} \right)}
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">L</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathrm mtight">dB</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="mord">10</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop"><span class="mop">lo<span style="margin-right:0.0139em;">g</span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.207em;"><span style="top:-2.4559em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">10</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2441em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">(</span></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3603em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.1389em;">P</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.1389em;">P</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.836em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">)</span></span></span></span></span></span></span></span></p>
<p><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>P</mi><mn>0</mn></msub></mrow><annotation encoding="application/x-tex">P_0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1389em;">P</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 是输入功率，<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>P</mi><mn>1</mn></msub></mrow><annotation encoding="application/x-tex">P_1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1389em;">P</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 是输出功率。当 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mstyle scriptlevel="0" displaystyle="false"><mfrac><msub><mi>P</mi><mn>1</mn></msub><msub><mi>P</mi><mn>0</mn></msub></mfrac></mstyle><mo>=</mo><mstyle scriptlevel="0" displaystyle="false"><mfrac><mn>1</mn><mn>2</mn></mfrac></mstyle></mrow><annotation encoding="application/x-tex">\tfrac{P_1}{P_0} = \tfrac{1}{2}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.3335em;vertical-align:-0.4451em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8884em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.1389em;">P</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3173em;"><span style="top:-2.357em;margin-left:-0.1389em;margin-right:0.0714em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.143em;"><span></span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.4101em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.1389em;">P</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3173em;"><span style="top:-2.357em;margin-left:-0.1389em;margin-right:0.0714em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.143em;"><span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.4451em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.1901em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8451em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span> 时，<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>L</mi><mrow><mi mathvariant="normal">d</mi><mi mathvariant="normal">B</mi></mrow></msub><mo>≈</mo><mo>−</mo><mn>3.01</mn><mtext>&nbsp;dB</mtext></mrow><annotation encoding="application/x-tex">L_{\mathrm{dB}} \approx -3.01 \text{ dB}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">L</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathrm mtight">dB</span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7778em;vertical-align:-0.0833em;"></span><span class="mord">−</span><span class="mord">3.01</span><span class="mord text"><span class="mord">&nbsp;dB</span></span></span></span></span>，正好对应信号功率衰减到最大功率的一半。在该点上，电阻的阻值等于电容的容抗，即：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>R</mi><mo>=</mo><msub><mi>X</mi><mi>C</mi></msub></mrow><annotation encoding="application/x-tex">R = X_C
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.0077em;">R</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0785em;">X</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.0785em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.0715em;">C</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></span></p>
<p>代入容抗的计算公式，即可得到截止频率：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>f</mi><mo>=</mo><mfrac><mn>1</mn><mrow><mn>2</mn><mi>π</mi><mi>R</mi><mi>C</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">f = \frac{1}{2\pi RC}
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.0074em;vertical-align:-0.686em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3214em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span><span class="mord mathnormal" style="margin-right:0.0359em;">π</span><span class="mord mathnormal" style="margin-right:0.0077em;">R</span><span class="mord mathnormal" style="margin-right:0.0715em;">C</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p>
<h2 id="信号采样与分析" tabindex="-1">信号采样与分析</h2>
<p>为了对芯片输出的音频信号进行频域分析，需要准确采样信号。这里我用一块 USB 声卡配合 Adobe Audition 和示波器进行录制与分析。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/Oscilloscope.avif" alt="最近购入了一台普源 DHO814 四通道示波器，12bit 储存深度，最高 1.25GSa/s 的采样率。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>最近购入了一台普源 DHO814 四通道示波器，12bit 储存深度，最高 1.25GSa/s 的采样率。</span>
		</figcaption>
	
</figure><p></p>
<p>操作时有个关键的技术细节，信号必须接在声卡的 <span style="color: #6cace4">LINE IN</span>（线路输入）接口上，而不是 <span style="color: #e68699">MIC IN</span>（麦克风输入），因为两者的电平定义完全不同。</p>
<p>与带有预放大功能的 MIC IN 不同，LINE IN 专为高电平信号设计。大多数的 PSG 芯片输出信号都会直接用于驱动小型的模拟电路或扬声器，它们通常在 0 V 到 5 V 之间做单极性跳变，峰峰值 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>V</mi><mrow><mi>p</mi><mo>−</mo><mi>p</mi></mrow></msub></mrow><annotation encoding="application/x-tex">V_{p-p}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.9694em;vertical-align:-0.2861em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.2222em;">V</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2583em;"><span style="top:-2.55em;margin-left:-0.2222em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">p</span><span class="mbin mtight">−</span><span class="mord mathnormal mtight">p</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span></span> 能达到 2 V 甚至 4 V。然而，消费级声卡预期的输入电平应该是 -10 dBV。这个单位是以 1 V 为基准参考的电压值。根据换算公式：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>dBV</mtext><mo>=</mo><mn>20</mn><mo>⋅</mo><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>10</mn></msub><mrow><mo fence="true">(</mo><mfrac><mi>V</mi><mn>1.000</mn></mfrac><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\text{dBV} = 20\cdot\log_{10}\left(\frac{V}{1.000}\right)
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord text"><span class="mord">dBV</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">20</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="mop"><span class="mop">lo<span style="margin-right:0.0139em;">g</span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.207em;"><span style="top:-2.4559em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">10</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2441em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">(</span></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3603em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1.000</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.2222em;">V</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">)</span></span></span></span></span></span></span></p>
<p>可以计算出对应的有效值电压约为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>0.316</mn><msub><mtext>&nbsp;V</mtext><mtext>RMS</mtext></msub></mrow><annotation encoding="application/x-tex">0.316\text{ V}_\text{RMS}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord">0.316</span><span class="mord"><span class="mord text"><span class="mord">&nbsp;V</span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord text mtight"><span class="mord mtight">RMS</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>.</p>
<p><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mtext>&nbsp;V</mtext><mtext>RMS</mtext></msub></mrow><annotation encoding="application/x-tex">\text{ V}_\text{RMS}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord text"><span class="mord">&nbsp;V</span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord text mtight"><span class="mord mtight">RMS</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 指的是均方根 (Root Mean Square) 值，是衡量随时间变化的电压有效性的核心指标。比如正弦波为的有效值约等于峰值电压的 0.707 倍（即 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mstyle scriptlevel="0" displaystyle="false"><mfrac><mn>1</mn><msqrt><mn>2</mn></msqrt></mfrac></mstyle></mrow><annotation encoding="application/x-tex">\tfrac{1}{ \sqrt{2} }</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.3831em;vertical-align:-0.538em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8451em;"><span style="top:-2.551em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord sqrt mtight"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.9128em;"><span class="svg-align" style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mtight" style="padding-left:0.833em;"><span class="mord mtight">2</span></span></span><span style="top:-2.8728em;"><span class="pstrut" style="height:3em;"></span><span class="hide-tail mtight" style="min-width:0.853em;height:1.08em;"><svg xmlns="http://www.w3.org/2000/svg" width="400em" height="1.08em" viewBox="0 0 400000 1080" preserveAspectRatio="xMinYMin slice"><path d="M95,702
c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,-10,-9.5,-14
c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54
c44.2,-33.3,65.8,-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10
s173,378,173,378c0.7,0,35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429
c69,-144,104.5,-217.7,106.5,-221
l0 -0
c5.3,-9.3,12,-14,20,-14
H400000v40H845.2724
s-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7
c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47z
M834 80h400000v40h-400000z"></path></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.1272em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.538em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span>）；而理想方波信号的有效值就等于它的峰值。由此可见，芯片输出的原始信号强度远超过声卡的额定承受范围，直接输入会导致严重<strong>削波失真 (Clipping)</strong>，也就是在电平表上看到的「爆音」。</p>
<p>为了安全且完整地采集信号，需要设计一个简单的调理电路。芯片输出通常携带 2.5 V 左右的直流偏置，这会偏置声卡的输入级。首先串联一个电容器，它在电路中起到了<strong>高通滤波器</strong>的作用，只允许变化的音频成分通过，阻断直流成分。</p>
<p>随后再引入大电阻分压网络来进一步衰减信号强度，将 5 V 的 TTL 电平降压至 0.3 V 左右。这里就涉及到<strong>阻抗匹配 (Impedance Matching)</strong> 的概念。信号传输可以简化为一个串联分压模型：</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/Series_Module.avif" alt="信号传输可以简化为一个串联分压电路。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>信号传输可以简化为一个串联分压电路。</span>
		</figcaption>
	
</figure>
其公式为：<p></p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>V</mi><mrow><mi>i</mi><mi>n</mi></mrow></msub><mo>=</mo><msub><mi>V</mi><mrow><mi>s</mi><mi>r</mi><mi>c</mi></mrow></msub><mo>⋅</mo><mfrac><msub><mi>Z</mi><mrow><mi>i</mi><mi>n</mi></mrow></msub><mrow><msub><mi>Z</mi><mrow><mi>o</mi><mi>u</mi><mi>t</mi></mrow></msub><mo>+</mo><msub><mi>Z</mi><mrow><mi>i</mi><mi>n</mi></mrow></msub></mrow></mfrac></mrow><annotation encoding="application/x-tex">V_{in} = V_{src}\cdot\frac{ Z_{in} }{ Z_{out}+Z_{in} }
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.2222em;">V</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3117em;"><span style="top:-2.55em;margin-left:-0.2222em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">in</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.2222em;">V</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.2222em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.0278em;">sr</span><span class="mord mathnormal mtight">c</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:2.1963em;vertical-align:-0.836em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3603em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.0715em;">Z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2806em;"><span style="top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">o</span><span class="mord mathnormal mtight">u</span><span class="mord mathnormal mtight">t</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0715em;">Z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3117em;"><span style="top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">in</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.0715em;">Z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3117em;"><span style="top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">in</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.836em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p>
<p>其中，<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>V</mi><mrow><mi>s</mi><mi>r</mi><mi>c</mi></mrow></msub></mrow><annotation encoding="application/x-tex">V_{src}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.2222em;">V</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.2222em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.0278em;">sr</span><span class="mord mathnormal mtight">c</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 和 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>Z</mi><mrow><mi>o</mi><mi>u</mi><mi>t</mi></mrow></msub></mrow><annotation encoding="application/x-tex">Z_{out}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0715em;">Z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2806em;"><span style="top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">o</span><span class="mord mathnormal mtight">u</span><span class="mord mathnormal mtight">t</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 为信号内部的理想电压和输出端的阻抗（源阻抗），而 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>Z</mi><mrow><mi>i</mi><mi>n</mi></mrow></msub></mrow><annotation encoding="application/x-tex">Z_{in}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0715em;">Z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3117em;"><span style="top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">in</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 和 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>V</mi><mrow><mi>i</mi><mi>n</mi></mrow></msub></mrow><annotation encoding="application/x-tex">V_{in}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.2222em;">V</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3117em;"><span style="top:-2.55em;margin-left:-0.2222em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">in</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 为输入端的阻抗（负载阻抗）和声卡实际接收到的电压。为了让声卡尽可能完整的接收到芯片产生的电压信号，即让 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>V</mi><mrow><mi>i</mi><mi>n</mi></mrow></msub></mrow><annotation encoding="application/x-tex">V_{in}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.2222em;">V</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3117em;"><span style="top:-2.55em;margin-left:-0.2222em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">in</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 尽可能接近 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>V</mi><mrow><mi>s</mi><mi>r</mi><mi>c</mi></mrow></msub></mrow><annotation encoding="application/x-tex">V_{src}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.2222em;">V</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.2222em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.0278em;">sr</span><span class="mord mathnormal mtight">c</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>，那么 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>Z</mi><mrow><mi>i</mi><mi>n</mi></mrow></msub></mrow><annotation encoding="application/x-tex">Z_{in}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0715em;">Z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3117em;"><span style="top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">in</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 应该尽可能大。当 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>Z</mi><mrow><mi>i</mi><mi>n</mi></mrow></msub><mo>→</mo><mo>+</mo><mi mathvariant="normal">∞</mi></mrow><annotation encoding="application/x-tex">Z_{in}\to+\infty</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0715em;">Z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3117em;"><span style="top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">in</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">→</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6667em;vertical-align:-0.0833em;"></span><span class="mord">+</span><span class="mord">∞</span></span></span></span> 时，<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>V</mi><mrow><mi>i</mi><mi>n</mi></mrow></msub><mo>=</mo><msub><mi>V</mi><mrow><mi>s</mi><mi>r</mi><mi>c</mi></mrow></msub></mrow><annotation encoding="application/x-tex">V_{in}=V_{src}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.2222em;">V</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3117em;"><span style="top:-2.55em;margin-left:-0.2222em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">in</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.2222em;">V</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.2222em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.0278em;">sr</span><span class="mord mathnormal mtight">c</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>. 通俗点说，输出阻抗越低，信号源的「推力」就越强，信号也就越不容易在传输过程中损耗。</p>
<p>为了观察滤波器的实际效果，先录制一段未处理的音符 C4（注意音量大小）：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C++</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C++">note.<span class="hljs-built_in">PlayNoteEvent</span>(<span class="hljs-number">500</span>, NOTE_C4, <span class="hljs-number">15</span>);
</code></pre>
	</div>
</div>

<p></p><div class="audio-container">
	<audio class="player-body" src="/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/audio/unfiltered.wav" preload="metadata" aria-label="unfiltered.wav">
		<source src="/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/audio/unfiltered.wav" type="audio/wav">
	</audio>

	<div class="audio-controls">
		<button class="play-pause-btn" aria-label="播放">
			<iconify-icon icon="material-symbols:play-arrow-rounded"></iconify-icon>
		</button>
		<div class="audio-info">
			<div class="title-container">
				<div class="title-track">
					<span class="audio-title">unfiltered.wav</span>
					<span class="audio-title duplicate" aria-hidden="true">unfiltered.wav</span>
				</div>
			</div>
			<small class="audio-state">PAUSED</small>
		</div>
		<div class="progress-section">
			<div class="progress-bar">
				<span class="time current" aria-hidden="true">0:00</span>
				<div class="seek-bar" role="slider" aria-label="播放进度条" aria-valuetext="播放进度，总长 0 分 0 秒" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
						<div class="seek-bar-bg"></div>
						<div class="seek-bar-fill"></div>
						<div class="seek-bar-handle"></div>
				</div>
				<span class="time total" aria-hidden="true">0:00</span>
			</div>
		</div>
		<div class="secondary-section">
			<button class="speed-btn" aria-label="播放速度: 1.0 倍">1.0x</button>
			<button class="mute-btn" aria-label="静音"><iconify-icon icon="material-symbols:volume-up-rounded"></iconify-icon></button>
			<button class="download-btn" aria-label="下载音频">
				<iconify-icon icon="material-symbols:download-rounded"></iconify-icon>
			</button>
		</div>
	</div>
</div><p></p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/Waveform_Unfiltered.avif" alt="未经处理的音频信号的波形。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>未经处理的音频信号的波形。</span>
		</figcaption>
	
</figure><p></p>
<p>随后，在信号输出端与地之间并联了一个 10nF 的陶瓷电容。这个元件与前级的限流电阻共同构成了一个一阶低通滤波器。在示波器上可以明显观察到，原本陡峭的方波边缘变得圆润了——这意味着高频谐波被削弱，信号在频域上的能量分布发生了显著变化。经过滤波的音频听上去要更「闷」一点。</p>
<p></p><div class="audio-container">
	<audio class="player-body" src="/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/audio/filtered.wav" preload="metadata" aria-label="filtered.wav">
		<source src="/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/audio/filtered.wav" type="audio/wav">
	</audio>

	<div class="audio-controls">
		<button class="play-pause-btn" aria-label="播放">
			<iconify-icon icon="material-symbols:play-arrow-rounded"></iconify-icon>
		</button>
		<div class="audio-info">
			<div class="title-container">
				<div class="title-track">
					<span class="audio-title">filtered.wav</span>
					<span class="audio-title duplicate" aria-hidden="true">filtered.wav</span>
				</div>
			</div>
			<small class="audio-state">PAUSED</small>
		</div>
		<div class="progress-section">
			<div class="progress-bar">
				<span class="time current" aria-hidden="true">0:00</span>
				<div class="seek-bar" role="slider" aria-label="播放进度条" aria-valuetext="播放进度，总长 0 分 0 秒" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
						<div class="seek-bar-bg"></div>
						<div class="seek-bar-fill"></div>
						<div class="seek-bar-handle"></div>
				</div>
				<span class="time total" aria-hidden="true">0:00</span>
			</div>
		</div>
		<div class="secondary-section">
			<button class="speed-btn" aria-label="播放速度: 1.0 倍">1.0x</button>
			<button class="mute-btn" aria-label="静音"><iconify-icon icon="material-symbols:volume-up-rounded"></iconify-icon></button>
			<button class="download-btn" aria-label="下载音频">
				<iconify-icon icon="material-symbols:download-rounded"></iconify-icon>
			</button>
		</div>
	</div>
</div><p></p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/Waveform_Filtered.avif" alt="在信号输出端与地之间并联了一个电容。可以看到方波的上升沿顶端和下降沿底端形成了圆角。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>在信号输出端与地之间并联了一个电容。可以看到方波的上升沿顶端和下降沿底端形成了圆角。</span>
		</figcaption>
	
</figure><p></p>
<p>由于输出端的内阻未知，可以通过逆向工程来获取系统的关键参数。利用示波器捕捉信号的阶跃响应。实测显示，信号的上升沿时间（10%-90%）<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mi>r</mi></msub></mrow><annotation encoding="application/x-tex">t_r</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.0278em;">r</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 为 40 μs 左右。由于示波器的输入阻抗很大（1 MΩ），远大于芯片的输出内阻，其并联效应对波形的影响可以忽略不计。对于一阶 RC 滤波器，上升时间 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mi>r</mi></msub></mrow><annotation encoding="application/x-tex">t_r</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.0278em;">r</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 与时间常数 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>τ</mi><mo>=</mo><mi>R</mi><mi>C</mi></mrow><annotation encoding="application/x-tex">\tau=RC</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.1132em;">τ</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.0077em;">R</span><span class="mord mathnormal" style="margin-right:0.0715em;">C</span></span></span></span> 满足如下关系：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>t</mi><mi>r</mi></msub><mo>=</mo><mn>2.2</mn><mo>⋅</mo><mi>τ</mi></mrow><annotation encoding="application/x-tex">t_r = 2.2\cdot \tau
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.0278em;">r</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">2.2</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.1132em;">τ</span></span></span></span></span></p>
<p>可得芯片的输出内阻约为 1.82 kΩ. 再代入截止频率的计算公式 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>f</mi><mi>c</mi></msub><mo>=</mo><mstyle scriptlevel="0" displaystyle="false"><mfrac><mn>1</mn><mrow><mn>2</mn><mi>π</mi><mi>R</mi><mi>C</mi></mrow></mfrac></mstyle></mrow><annotation encoding="application/x-tex">f_c=\tfrac{1}{2\pi RC}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">c</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.1901em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8451em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span><span class="mord mathnormal mtight" style="margin-right:0.0359em;">π</span><span class="mord mathnormal mtight" style="margin-right:0.0077em;">R</span><span class="mord mathnormal mtight" style="margin-right:0.0715em;">C</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span> 可以推导出：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>f</mi><mi>c</mi></msub><mo>≈</mo><mfrac><mn>0.35</mn><msub><mi>t</mi><mi>r</mi></msub></mfrac></mrow><annotation encoding="application/x-tex">f_c\approx\frac{0.35}{t_r}
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">c</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.1574em;vertical-align:-0.836em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3214em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.0278em;">r</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0.35</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.836em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p>
<p>得截止频率 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>f</mi><mi>c</mi></msub><mo>≈</mo><mn>8.75</mn><mtext>&nbsp;kHz</mtext></mrow><annotation encoding="application/x-tex">f_c\approx8.75\text{ kHz}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">c</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord">8.75</span><span class="mord text"><span class="mord">&nbsp;kHz</span></span></span></span></span>. 这个结果和 Audition 中频谱分析展示的结果非常接近。可以看到，信号在 8 kHz 附近恰好产生了约 -3 dB 的衰减。</p>
<p>受限于<strong>奈奎斯特采样定律 (Nyquist Sampling Theorem)</strong>，以常见的音频采样率 44.1 kHz 为准，声卡中的抗混叠滤波器会强制切除 22 kHz 以上的所有能量。因此在接近 20 kHz 时开始急速衰减，并在 22 kHz 之后衰减到 -100 dB 以下。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/Freq_Analysis.avif" alt="Audition 中对波形进行频率分析后的对比图，中红色曲线代表滤波前，蓝色曲线则是滤波后的效果。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>Audition 中对波形进行频率分析后的对比图，中红色曲线代表滤波前，蓝色曲线则是滤波后的效果。</span>
		</figcaption>
	
</figure><p></p>
<p>在频域的视角下，方波本质上是由无数正弦波叠加而成的复合信号。对于一个振幅为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>A</mi></mrow><annotation encoding="application/x-tex">A</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal">A</span></span></span></span>，频率为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>f</mi><mn>0</mn></msub></mrow><annotation encoding="application/x-tex">f_0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 的理想方波，其傅里叶级数展开式为：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>f</mi><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo><mo>=</mo><mfrac><mrow><mn>4</mn><mi>A</mi></mrow><mi>π</mi></mfrac><munderover><mo>∑</mo><mrow><mi>n</mi><mo>=</mo><mn>1</mn><mo separator="true">,</mo><mn>3</mn><mo separator="true">,</mo><mn>5</mn><mo separator="true">,</mo><mo>…</mo></mrow><mi mathvariant="normal">∞</mi></munderover><mfrac><mn>1</mn><mi>n</mi></mfrac><mi>sin</mi><mo>⁡</mo><mo stretchy="false">(</mo><mn>2</mn><mi>π</mi><mi>n</mi><msub><mi>f</mi><mn>0</mn></msub><mi>t</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">f(t)=\frac{4A}{\pi}\sum_{n=1, 3, 5, \dots}^{\infty}\frac{1}{n}\sin(2\pi nf_0t)
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:3.0546em;vertical-align:-1.4032em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3603em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0359em;">π</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">4</span><span class="mord mathnormal">A</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop op-limits"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.6514em;"><span style="top:-1.8829em;margin-left:0em;"><span class="pstrut" style="height:3.05em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">n</span><span class="mrel mtight">=</span><span class="mord mtight">1</span><span class="mpunct mtight">,</span><span class="mord mtight">3</span><span class="mpunct mtight">,</span><span class="mord mtight">5</span><span class="mpunct mtight">,</span><span class="minner mtight">…</span></span></span></span><span style="top:-3.05em;"><span class="pstrut" style="height:3.05em;"></span><span><span class="mop op-symbol large-op">∑</span></span></span><span style="top:-4.3em;margin-left:0em;"><span class="pstrut" style="height:3.05em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">∞</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.4032em;"><span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3214em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">n</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">sin</span><span class="mopen">(</span><span class="mord">2</span><span class="mord mathnormal" style="margin-right:0.0359em;">π</span><span class="mord mathnormal">n</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord mathnormal">t</span><span class="mclose">)</span></span></span></span></span></p>
<p>这个公式揭示了方波的三个本质：</p>
<ol>
<li>只包含奇次谐波：方波只包含基波（<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>f</mi><mn>0</mn></msub></mrow><annotation encoding="application/x-tex">f_0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span>）及其奇数倍频率（<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>3</mn><msub><mi>f</mi><mn>0</mn></msub><mo separator="true">,</mo><mn>5</mn><msub><mi>f</mi><mn>0</mn></msub><mo separator="true">,</mo><mn>7</mn><msub><mi>f</mi><mn>0</mn></msub><mo>…</mo></mrow><annotation encoding="application/x-tex">3f_0, 5f_0, 7f_0 \dots</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord">3</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">5</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">7</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner">…</span></span></span></span>）；</li>
<li>振幅衰减：谐波次数越高，能量越弱。第 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>n</mi></mrow><annotation encoding="application/x-tex">n</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">n</span></span></span></span> 次谐波的振幅是基波的 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>1</mn><mi mathvariant="normal">/</mi><mi>n</mi></mrow><annotation encoding="application/x-tex">1/n</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1/</span><span class="mord mathnormal">n</span></span></span></span>；</li>
<li>相位对齐：所有的正弦波在零点同时出发，通过叠加抵消，最终在时域上构建出平整的顶部和陡峭的边缘。</li>
</ol>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/Square_Wave_FFT.avif" alt="利用示波器上 FFT 运算得到的图像。为了让波峰更清晰，垂直刻度采用了 VRMS." onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>利用示波器上 FFT 运算得到的图像。为了让波峰更清晰，垂直刻度采用了 V<sub>RMS</sub>.</span>
		</figcaption>
	
</figure><p></p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/Fourier_Series_And_Transform.avif" alt="函数 s6xs6xs6x（红色曲线）是 6 个谐波相关正弦波（蓝色曲线）的傅里叶级数求和结果。其傅里叶变换&nbsp;SfSfSf 是一种频域表示，揭示了这些叠加正弦波的振幅分布。Wikipedia" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>函数 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi><mn>6</mn><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">s6(x)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">s</span><span class="mord">6</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mclose">)</span></span></span></span>（红色曲线）是 6 个谐波相关正弦波（蓝色曲线）的傅里叶级数求和结果。其傅里叶变换&nbsp;<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>S</mi><mo stretchy="false">(</mo><mi>f</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">S(f)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.0576em;">S</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mclose">)</span></span></span></span> 是一种频域表示，揭示了这些叠加正弦波的振幅分布。<a href="https://en.wikipedia.org/wiki/Fourier_series" class="external-link" target="_blank">Wikipedia</a></span>
		</figcaption>
	
</figure><p></p>
<p>在试图用有限项谐波拟合这一理想模型时，在跳变的边缘处总会出现一圈无法消除的振铃。这种现象被称为<strong>吉布斯现象 (Gibbs Phenomenon)</strong>。即便将谐波项数增加到更多，边缘过冲的幅度依然会锁定在 9% 左右。</p>
<p>现实世界中不存在绝对完美的方波。受限于系统的带宽限制，高频成分在传输中必然被截断，导致波形转角处不可避免地出现圆角或过冲。这也是为什么示波器捕捉到的信号，永远无法呈现出理论模型中那种绝对锐利的直角。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2026-04-26-Spectrum-Analysis-and-Filter-Design/img/Gibbs_Phenomenon.avif" alt="从上至下依次为使用 5, 25, 125 次谐波对方波进行函数逼近的效果。Wikipedia" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>从上至下依次为使用 5, 25, 125 次谐波对方波进行函数逼近的效果。<a href="https://en.wikipedia.org/wiki/Gibbs_phenomenon" class="external-link" target="_blank">Wikipedia</a></span>
		</figcaption>
	
</figure><p></p>
<h2 id="总结" tabindex="-1">总结</h2>
<p>这篇文章主要侧重于理论和电路上的知识。讲的东西已经够多了，从傅里叶变换到滤波器设计，最后再到信号采样与分析，虽然只是稍作介绍，途中也学到了不少新知识。此后将开始尝试解析 Tracker Music 的文件格式，并写出能够播放它们的驱动程序。对于一些有更多轨道的 chiptune，可能会考虑使用性能更强大的单片机，如 ESP32 等。</p>
<h2 id="扩展阅读" tabindex="-1">扩展阅读</h2>
<ol>
<li>Fourier transform. <em>Wikipedia</em>. <a href="https://en.wikipedia.org/wiki/Fourier_transform" class="external-link" target="_blank">https://en.wikipedia.org/wiki/Fourier_transform</a></li>
<li>Fourier series. <em>Wikipedia</em>. <a href="https://en.wikipedia.org/wiki/Fourier_series" class="external-link" target="_blank">https://en.wikipedia.org/wiki/Fourier_series</a></li>
<li>Fast fourier transform. <em>Wikipedia</em>. <a href="https://en.wikipedia.org/wiki/Fast_fourier_transform" class="external-link" target="_blank">https://en.wikipedia.org/wiki/Fast_fourier_transform</a></li>
<li>Window function. <em>Wikipedia</em>. <a href="https://en.wikipedia.org/wiki/Window_function" class="external-link" target="_blank">https://en.wikipedia.org/wiki/Window_function</a></li>
<li>Spectral leakage. <em>Wikipedia</em>. <a href="https://en.wikipedia.org/wiki/Spectral_leakage" class="external-link" target="_blank">https://en.wikipedia.org/wiki/Spectral_leakage</a></li>
</ol>
</body></html>
						]]>
					</content:encoded>
				</item>
			

				<item>
					<title>驱动 LCD12864 ST7920（二）：图像的显示</title>
					<link>https://studiountagged.top/articles/2025-10-27-LCD12864-ST7920-Disp-Img</link>
					<guid isPermaLink="true">https://studiountagged.top/articles/2025-10-27-LCD12864-ST7920-Disp-Img/</guid>
					<pubDate>Sun, 26 Oct 2025 16:00:00 GMT</pubDate>
					<author>Steven Liu</author>
					
					<description>
						<![CDATA[
							在 LCD 12864 ST7920 上显示图像。
Display an image on LCD12864 ST7920.
							
						]]>
					</description>
					<content:encoded>
						<![CDATA[
							<html><head></head><body><h2 id="前言" tabindex="-1">前言</h2>
<p>在<a href="/articles/2025-09-11-LCD12864-ST7920/">上一篇文章</a>中，已经实现了驱动这块屏幕的基本函数，使用的主要是字符模式，即往显存中写入内建字库的字符地址。而在本文章中，则会使用它的图形模式来实现各种显示效果。</p>
<h2 id="硬件" tabindex="-1">硬件</h2>
<p>如果要显示图片之类的内容，则必须进入图形模式，这样单独控制每个像素点的亮灭。那么，要怎么才能进入图形模式呢？根据手册上的介绍，功能设置指令的第 2 位<code>RE</code>低电平为基本指令集，高电平为扩展指令集。而图形模式则仅能在启用了扩展指令集的时候才能被切换。启用之后，还需要将 bit1 的<code>G</code>切换为高电平。这样一来，就能启用图形模式了。请注意，bit4 的<code>DL</code>，bit2 的<code>RE</code>以及 bit1 的<code>G</code>不能同时设置。先要改变<code>DL</code>或<code>G</code>，才能再是<code>RE</code>。要输入的指令如下：</p>
<div class="table-container"><table>
<thead>
<tr>
<th><span>指令</span><span>Instruction</span></th>
<th style="text-align:center">RS</th>
<th style="text-align:center">RW</th>
<th style="text-align:center">D7</th>
<th style="text-align:center">D6</th>
<th style="text-align:center">D5</th>
<th style="text-align:center">D4</th>
<th style="text-align:center">D3</th>
<th style="text-align:center">D2</th>
<th style="text-align:center">D1</th>
<th style="text-align:center">D0</th>
</tr>
</thead>
<tbody>
<tr>
<td><span>功能设置</span><span>Function Set</span></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>1</code></td>
<td style="text-align:center"><code>1</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>1</code></td>
<td style="text-align:center"><code>1</code></td>
<td style="text-align:center"><code>0</code></td>
</tr>
</tbody>
</table>
</div><p>这样，通过调用之前写好的函数<code>FuncSet()</code>即可切换成图形模式：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C"><span class="hljs-comment">// Function Set command</span>
<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">DataLength</span> &nbsp; &nbsp; {</span> DL_8BIT &nbsp; = <span class="hljs-number">1</span>, DL_4BIT &nbsp; &nbsp;= <span class="hljs-number">0</span> };
<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">InstructionSet</span> {</span> EXTENDED &nbsp;= <span class="hljs-number">1</span>, BASIC &nbsp; &nbsp; &nbsp;= <span class="hljs-number">0</span> };
<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">GraphicMode</span> &nbsp; &nbsp;{</span> GRAPHIC &nbsp; = <span class="hljs-number">1</span>, CHAR &nbsp; &nbsp; &nbsp; = <span class="hljs-number">0</span> };

FuncSet(DL_8BIT, EXTENDED, GRAPHIC);
</code></pre>
	</div>
</div>

<h2 id="软件" tabindex="-1">软件</h2>
<p>那么，屏幕是如何显示图像的呢？ST7920 通过 GDRAM (Graphic Display RAM) 储存图像数据。但是内存与像素点的对应关系略有些复杂。总的来说，ST7920 是以 16 位（即两个字节）为最小单位进行存储和寻址的，对应控制屏幕上一个宽 16 px，高 1 px 的区域。而屏幕的数据位宽度只有 8 bit（一个字节），因此，每个地址需要进行两次写入操作。对于一个大小为 128 × 64 px 的屏幕，所有的像素点并非独立寻址，而是被划分成 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mn>128</mn><mn>16</mn></mfrac><mo>×</mo><mfrac><mn>64</mn><mn>1</mn></mfrac><mo>=</mo><mn>8</mn><mo>×</mo><mn>64</mn></mrow><annotation encoding="application/x-tex">\frac{128}{16} \times \frac{64}{1} = 8 \times 64</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.1901em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8451em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">16</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">128</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1.1901em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8451em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">64</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7278em;vertical-align:-0.0833em;"></span><span class="mord">8</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">64</span></span></span></span> 个这样的小块。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-10-27-LCD12864-ST7920-Disp-Img/img/LCD12864_Display_1.avif" alt="屏幕上的像素点是以宽 16 px，高 1 px 的小块形式储存在 GDRAM 中的。每个小块都有唯一的地址。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>屏幕上的像素点是以宽 16 px，高 1 px 的小块形式储存在 GDRAM 中的。每个小块都有唯一的地址。</span>
		</figcaption>
	
</figure><p></p>
<p>ST7920 有个特殊之处，即整个屏幕的地址并非连续的，而是被拆分成两个独立区域。请注意，不同型号的屏幕拆分方式可能会有差异。笔者使用的这块屏幕是拆分成了上下两个区域，每个区域的大小为 128 × 32 px。有些屏幕会拆分成左右两块 64 × 64 px 大小的区域。因此实际操作时请务必查清楚手册。在这里面，上半屏的水平地址从<code>0x80</code>开始，递增至<code>0x87</code>；下半屏从<code>0x88</code>开始，递增至<code>0x8F</code>。而每个水平地址中又包含 32 个垂直地址，都是从<code>0x80</code>开始，<code>0x9F</code>结束。也就是说，定位一个 chunk 需要依次发送两条命令用来在 GDRAM 中定位。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-10-27-LCD12864-ST7920-Disp-Img/img/LCD12864_Display_2.avif" alt="屏幕每一小块的地址示意图。屏幕被分为上下两个部分，垂直地址从 0x80 开始递增至 0x9F，而位于同一行小块的列地址不变。水平地址上半部分从 0x80 开始，下半从 0x88 开始。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>屏幕每一小块的地址示意图。屏幕被分为上下两个部分，垂直地址从 0x80 开始递增至 0x9F，而位于同一行小块的列地址不变。水平地址上半部分从 0x80 开始，下半从 0x88 开始。</span>
		</figcaption>
	
</figure><p></p>
<p>这些小块在 GDRAM 中有唯一的字节地址。当我们要显示图像时，只需先通过指令指定要操作的 GDRAM 地址，然后向该地址<strong>写入</strong>对应的数据，即可控制这些像素点的亮灭。根据手册，寻址操作需要依次输入两条命令分别设置垂直和水平地址：</p>
<div class="table-container"><table>
<thead>
<tr>
<th><span>指令</span><span>Instruction</span></th>
<th style="text-align:center">RS</th>
<th style="text-align:center">RW</th>
<th style="text-align:center">D7</th>
<th style="text-align:center">D6</th>
<th style="text-align:center">D5</th>
<th style="text-align:center">D4</th>
<th style="text-align:center">D3</th>
<th style="text-align:center">D2</th>
<th style="text-align:center">D1</th>
<th style="text-align:center">D0</th>
</tr>
</thead>
<tbody>
<tr>
<td><span>设置垂直 GDRAM 地址</span><span>Set Verticle GDRAM Addr</span></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>1</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>AC5</code></td>
<td style="text-align:center"><code>AC4</code></td>
<td style="text-align:center"><code>AC3</code></td>
<td style="text-align:center"><code>AC2</code></td>
<td style="text-align:center"><code>AC1</code></td>
<td style="text-align:center"><code>AC0</code></td>
</tr>
<tr>
<td><span>设置水平 GDRAM 地址</span><span>Set Horizontal GDRAM Addr</span></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>1</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>AC3</code></td>
<td style="text-align:center"><code>AC2</code></td>
<td style="text-align:center"><code>AC1</code></td>
<td style="text-align:center"><code>AC0</code></td>
</tr>
</tbody>
</table>
</div><p><code>ACx</code>就是地址计数器（Address Counter）。由此也可以看出，垂直地址的范围为<code>0x00-0x3F</code>，水平地址的范围为<code>0x00-0x0F</code>。现在就可以定义函数<code>SetGraphicAddr()</code>，传入小块所在的行和列的地址，以此来寻址：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C"><span class="hljs-comment">// Set the Graphic RAM (GDRAM) Address</span>
<span class="hljs-type">void</span> <span class="hljs-title function_">SetGraphicAddr</span><span class="hljs-params">(<span class="hljs-type">uint8_t</span> x, <span class="hljs-type">uint8_t</span> y)</span> {
&nbsp; &nbsp; <span class="hljs-comment">// Set vertical address</span>
&nbsp; &nbsp; WriteByte(INST, <span class="hljs-number">0x80</span> | (y &amp; <span class="hljs-number">0x3F</span>), <span class="hljs-number">10</span>);
&nbsp; &nbsp; <span class="hljs-comment">// Set horizontal address</span>
&nbsp; &nbsp; WriteByte(INST, <span class="hljs-number">0x80</span> | (x &amp; <span class="hljs-number">0x0F</span>), <span class="hljs-number">10</span>);
}
</code></pre>
	</div>
</div>

<p>指令码中的<code>0x80</code>即将 bit7 设为<code>1</code>。随后，将<code>y</code>与<code>0x3F</code>取<code>&amp;</code>按位与运算。为什么要在此处进行这个运算呢？这种方法叫做<strong>位掩码（Bit Mask）</strong>，即选取要对哪些位进行操作。要操作的位就设为<code>1</code>，无需操作的就设为<code>0</code>。由于垂直地址的范围为<code>0x00-0x3F</code>，因此只需要对 bit0 - bit5 这 6 位进行操作。这样就算传入错误的数据，也不会向硬件发送无效地址。而对于<code>x</code>也是一样的，选取 bit0 - bit3 这 4 位进行操作。这样就可以准确地设定好要写入数据的地址了。</p>
<p>除了上面提到的内容以外，目前容易混淆的点还有如下几个：</p>
<ol>
<li>先写入的是高字节还是低字节？</li>
<li>先填充完高字节/低字节以后，另一半的数据要怎么写入？</li>
<li>指针会在完成一个水平地址的写入以后，自动指向下一个地址吗？</li>
</ol>
<p>这些问题其实手册上已经回答了，但是为了能够更加清晰地展示，笔者写了如下的测试程序：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C"><span class="hljs-type">void</span> <span class="hljs-title function_">setup</span><span class="hljs-params">()</span> {
&nbsp; &nbsp; Serial.begin(<span class="hljs-number">9600</span>);
&nbsp; &nbsp; LCD_Reset();
&nbsp; &nbsp; LCD_Initialize();

&nbsp; &nbsp; LCD_FuncSet(DL_8BIT, EXTENDED, GRAPHIC);
&nbsp; &nbsp; Serial.println(<span class="hljs-string">"Start Test..."</span>);

&nbsp; &nbsp; LCD_ClearGdram();
&nbsp; &nbsp; delay(<span class="hljs-number">1000</span>);

&nbsp; &nbsp; <span class="hljs-comment">// Phase 1: Phase 1: Higher byte test on addrX 0x80</span>
&nbsp; &nbsp; LCD_SetGdramAddr(<span class="hljs-number">0x80</span>, <span class="hljs-number">0x80</span>);

&nbsp; &nbsp; <span class="hljs-comment">// Write first byte 0xFF</span>
&nbsp; &nbsp; LCD_WriteByte(DATA, <span class="hljs-number">0xFF</span>, <span class="hljs-number">100</span>);
&nbsp; &nbsp; Serial.println(<span class="hljs-string">"Phase 1: Written 1st Byte to X=0. Expect: Pixels 0-7 ON."</span>);
&nbsp; &nbsp; delay(<span class="hljs-number">2000</span>);

&nbsp; &nbsp; <span class="hljs-comment">// Phase 2: Lower byte test on addrX 0x80</span>
&nbsp; &nbsp; LCD_WriteByte(DATA, <span class="hljs-number">0xF0</span>, <span class="hljs-number">100</span>);
&nbsp; &nbsp; Serial.println(<span class="hljs-string">"Phase 2: Written 2nd Byte to X=0. Expect: Pixels 8-15 show 11110000."</span>);
&nbsp; &nbsp; delay(<span class="hljs-number">2000</span>);

&nbsp; &nbsp; <span class="hljs-comment">// Phase 3: Address automatically increment</span>
&nbsp; &nbsp; LCD_WriteByte(DATA, <span class="hljs-number">0xAA</span>, <span class="hljs-number">100</span>); <span class="hljs-comment">// 10101010</span>
&nbsp; &nbsp; LCD_WriteByte(DATA, <span class="hljs-number">0x55</span>, <span class="hljs-number">100</span>); <span class="hljs-comment">// 01010101</span>
&nbsp; &nbsp; Serial.println(<span class="hljs-string">"Phase 3: Written 2 Bytes to X=1 (Auto-increment). Expect: Pixels 16-31 show pattern."</span>);
}
</code></pre>
	</div>
</div>

<ul>
<li><strong>第一阶段</strong> 测试水平地址的高位。这会让X = 0 到 X = 7 处的像素全部点亮。</li>
</ul>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-10-27-LCD12864-ST7920-Disp-Img/img/Phase_1.avif" alt="测试的第一阶段。X = 0 到 X = 7 处的像素全部点亮。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>测试的第一阶段。X = 0 到 X = 7 处的像素全部点亮。</span>
		</figcaption>
	
</figure><p></p>
<ul>
<li><strong>第二阶段</strong> 测试水平地址的低位。不再重新设置地址，而是接着写入。写入的数据是<code>0xF0</code>，可以看到 X = 8 到 X = 11 处的像素被点亮。</li>
</ul>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-10-27-LCD12864-ST7920-Disp-Img/img/Phase_2.avif" alt="测试的第二阶段。X = 8 到 X = 11 处的像素被点亮。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>测试的第二阶段。X = 8 到 X = 11 处的像素被点亮。</span>
		</figcaption>
	
</figure><p></p>
<ul>
<li><strong>第三阶段</strong> 测试地址自动递增。在不设置新地址的前提下，继续写入新的数据。可以看到屏幕上 X = 16 到 X = 23 处像素以<code>10101010</code>的方式点亮；X = 24 到 X = 31 处像素以<code>01010101</code>的方式点亮。</li>
</ul>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-10-27-LCD12864-ST7920-Disp-Img/img/Phase_3.avif" alt="测试的第三阶段。X = 16 到 X = 23 处像素以10101010的方式点亮；X = 24 到 X = 31 处像素以01010101的方式点亮。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>测试的第三阶段。X = 16 到 X = 23 处像素以<code>10101010</code>的方式点亮；X = 24 到 X = 31 处像素以<code>01010101</code>的方式点亮。</span>
		</figcaption>
	
</figure><p></p>
<p>对于第一个问题，阶段二输入<code>1111 0000</code>，而结果是 X = 8 到 X = 11 处的像素，也就是这个字节内靠左的部分被点亮。这就可以说明像素是按照**大端序（Big Endian）**的方式组织的。</p>
<p>在这里插一嘴计算机中字节储存（Endianness），这是个基础又关键的概念，它定义了多字节数据在存储或传输时的排列顺序。这个名字的来源很有意思，源自英国作家乔纳森·斯威夫创作的长篇小说《格列佛游记》中小人国的居民因争论该从大端还是小端敲开鸡蛋而分成两派，引发了一场荒诞的战争，后来这个典故被计算机科学家 Danny Cohen 在他的论文《论圣战与求和 <em>On Holy Wars and a Plea for Peace</em>》中借用，用来命名多字节数据存储时的分歧，这也就是大端序和小端序的来历。<sup class="footnote-ref"><a href="#footnote1">[1]</a><a class="footnote-anchor" id="footnote-ref1"></a></sup>简单来说，大端序就是一个数的最低位存在最高位的地址上，而小端序则恰恰相反。例如对于一个这样的整数<code>0xAABBCCDD</code>：</p>
<div class="table-container"><table>
<thead>
<tr>
<th><span>字节地址</span><span>Byte Address</span></th>
<th style="text-align:center">0</th>
<th style="text-align:center">1</th>
<th style="text-align:center">2</th>
<th style="text-align:center">3</th>
</tr>
</thead>
<tbody>
<tr>
<td><span>小端序</span><span>Small Endian</span></td>
<td style="text-align:center"><code>0xDD</code></td>
<td style="text-align:center"><code>0xCC</code></td>
<td style="text-align:center"><code>0xBB</code></td>
<td style="text-align:center"><code>0xAA</code></td>
</tr>
<tr>
<td><span>大端序</span><span>Big Endian</span></td>
<td style="text-align:center"><code>0xAA</code></td>
<td style="text-align:center"><code>0xBB</code></td>
<td style="text-align:center"><code>0xCC</code></td>
<td style="text-align:center"><code>0xDD</code></td>
</tr>
</tbody>
</table>
</div><p>回到正题，通过测试的结果，我们可以得出剩下的问题的答案：
r</p>
<ol>
<li>每个水平地址要写入两次，写入低八位时无需设置新的地址；</li>
<li>写完一个水平地址中的数据后，指针会自动指向下一地址。</li>
</ol>
<p>在研究清楚了屏幕的显示原理以后，接下来就是图像的数据。目前有很多将位图转化为二进制数据的取模软件，用什么软件就随意。在这个项目中，我是用的 <a href="https://javl.github.io/image2cpp/" class="external-link" target="_blank">image2cpp</a>这个项目，可以直接在浏览器中运行，无需额外安装，而且可以直接生成适用于 Arduino 的数据结构。笔者绘制了工作室 Logo 的像素画版本：</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-10-27-LCD12864-ST7920-Disp-Img/img/Studio_Untagged_Logo.avif" alt="像素画版本的 Logo。尽可能地保留了原 Logo 的特色。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>像素画版本的 Logo。尽可能地保留了原 Logo 的特色。</span>
		</figcaption>
	
</figure><p></p>
<p>在 image2cpp 中打开要转换的位图以后，Image Settings 中还有一些设置。具体情况取决于想要的效果。由于位图是黑色背景的，因此要在这个设置里面将 Background color 设为 Black。同时在 Output 中也要将 Code output format 设为 Arduino Code, single bitmap。点击最下方的 Generate Code 按钮后就可以了。</p>
<p>其实严格来说，这类数据最好储存在 SD 卡或者 ROM 中，避免占用单片机过多的空间。关于如何烧录、读取 EEPROM，使用 SD 卡模块，以后会有文章进行介绍。由于篇幅限制，在本篇文章中将直接将这部分数据放在头文件<code>logo_image.h</code>中。生成的内容大致如下：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C"><span class="hljs-type">const</span> <span class="hljs-type">unsigned</span> <span class="hljs-type">char</span> epd_bitmap_Sprite_0002 [] PROGMEM = {
&nbsp; &nbsp; <span class="hljs-number">0x00</span>, <span class="hljs-number">0x00</span>, <span class="hljs-number">0x00</span>, <span class="hljs-number">0x00</span>, <span class="hljs-number">0x00</span>, 
&nbsp; &nbsp; ...
};
</code></pre>
	</div>
</div>

<p>这一坨东西就是用来存放位图数据的数组。诸位读者可能注意到了，这个数组后面还有个<code>PROGMEM</code>的关键字。这是什么意思呢？Arduino 上有几种不同类型的内存，这些内存读写速度以及容量各不相同，各司其职：</p>
<ul>
<li>闪存（Flash），读写较快，存储编译后的程序代码和<code>const</code>常量；</li>
<li>静态随机存取存储器（SRAM），读写速度最快的，用于储存程序运行时创建和修改的变量、对象实例、堆栈等；</li>
<li>电可擦除可编程只读存储器（EEPROM），读写最慢，负责储存永久性配置参数等。</li>
</ul>
<p>由于 SRAM 的容量有限，例如在 Arduino Mega 2560 上仅为 8 KB，因此储存了图像数据的数组不应占用 SRAM 的空间。而 Flash 的容量是最大的，有 256 KB，因此可以将数组存放在此处。<code>PROGMEM</code>的作用正是将数组存放到 Flash 中。如果要读取数据，方式会和 SRAM 略有不同。这点会稍后提到。</p>
<p>如图所示，每行的地址从<code>0x80</code>开始，按照垂直向下的方向递增到<code>0x9F</code>，一共 32 行。而同一行里面的小块共用同一个列地址，即<code>0x80</code>。其他的列也是如此。在下半部分的第一列中，其列地址为<code>0x88</code>。在写入数据时应将两半部分分开处理。</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C"><span class="hljs-type">void</span> <span class="hljs-title function_">Lcd_PrintImg</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">unsigned</span> <span class="hljs-type">char</span>* bitmap)</span> {
	<span class="hljs-comment">// The upper part</span>
	<span class="hljs-keyword">for</span> (<span class="hljs-type">uint8_t</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">32</span>; i++) {
		SetGraphicAddr(<span class="hljs-number">0x80</span>, <span class="hljs-number">0x80</span> + i);
		<span class="hljs-keyword">for</span> (<span class="hljs-type">uint8_t</span> j = <span class="hljs-number">0</span>; j &lt; <span class="hljs-number">16</span>; j++) {
			<span class="hljs-type">uint8_t</span> data = pgm_read_byte(bitmap++);
			WriteByte(DATA, data, <span class="hljs-number">10</span>);
		}
	}

	<span class="hljs-comment">// The lower part</span>
	<span class="hljs-keyword">for</span> (<span class="hljs-type">uint8_t</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">32</span>; i++) {
		SetGraphicAddr(<span class="hljs-number">0x88</span>, <span class="hljs-number">0x80</span> + i);
		<span class="hljs-keyword">for</span> (<span class="hljs-type">uint8_t</span> j = <span class="hljs-number">0</span>; j &lt; <span class="hljs-number">16</span>; j++) {
			<span class="hljs-type">uint8_t</span> data = pgm_read_byte(bitmap++);
			WriteByte(DATA, data, <span class="hljs-number">10</span>);
		}
	}
}
</code></pre>
	</div>
</div>

<p>为了读取储存在 Flash 中的图像数据，须使用<code>pgm_read_byte()</code>。这个函数位于<code>&lt;avr/pgmspace.h&gt;</code>中。传参是一个地址。因此调用的时候传入<code>Lcd_PrintImg(epd_bitmap_Sprite_0002)</code>即可；而<code>bitmap++</code>的意思则是，当自增运算符<code>++</code>后置时，会先将<code>bitmap</code>当前的值传递给<code>pgm_read_byte()</code> 函数。函数执行完毕以后，才会对<code>bitmap</code>进行自增操作，这样就指针就自动指向了数组中下一字节的地址。</p>
<h2 id="最终效果" tabindex="-1">最终效果</h2>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-10-27-LCD12864-ST7920-Disp-Img/img/Logo_LCD.avif" alt="效果相当不错。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>效果相当不错。</span>
		</figcaption>
	
</figure><p></p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="footnote1" class="footnote-item"><p><a href="https://en.wikipedia.org/wiki/Endianness" class="external-link" target="_blank">Endianness - Wikipedia</a> <a href="#footnote-ref1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
</body></html>
						]]>
					</content:encoded>
				</item>
			

				<item>
					<title>独立游戏开发笔记（一）：序</title>
					<link>https://studiountagged.top/articles/2025-10-17-Indie-Game-Dev-Prologue</link>
					<guid isPermaLink="true">https://studiountagged.top/articles/2025-10-17-Indie-Game-Dev-Prologue/</guid>
					<pubDate>Thu, 16 Oct 2025 16:00:00 GMT</pubDate>
					<author>Steven Liu</author>
					
					<description>
						<![CDATA[
							独立游戏开发项目概述。
An overview of the indie game development project.
							
						]]>
					</description>
					<content:encoded>
						<![CDATA[
							<html><head></head><body><p>早在中学时期，我和 Roy 就有制作独立游戏的想法。不过由于当时由于学业等原因迟迟未能动工，而且制作游戏所需的各个方面的技能，例如编程、配乐、美术等等更是毫无经验，当时只是在纸上记录了一些剧情、世界观等方面的设定；现在从中学的学业压力解放以后，终于有空来好好研究这方面的技术。</p>
<p>最开始的时候，游戏是打算通过 Unity 来制作的。不过才开始没学多久，Unity 就捅出了 Unity Runtime Fee 这个「惊天」大篓子——虽然以我们当时（也包括写这篇文章的时候）从游戏中获得的收益水平尚未到达费用征收范围，但是考虑到 Unity 高层对此次事件的态度，以及造成的负面影响等等方面，我们最终还是放弃了它。市面上还有其他不少相当优秀的引擎，比如 Unreal，不过对于我们的游戏来说简直就是大炮轰蚊子；Godot 也因为这次事件获得了很多关注。不过在本项目中，我们最终还是决定基于 SDL3 手搓开发游戏引擎以及游戏。选择 SDL3 的原因是因为：</p>
<ol>
<li>SDL3 足够成熟，市面上有不少基于它的项目，教程和论坛资源也很丰富；</li>
<li>很多游戏引擎的组件，从渲染到场景、摄像机等等都需要涉及到很多图形学知识，而这些内容现在就需要自己写，在此过程中就可以更好地学习和运用这些知识；</li>
<li>SDL3 让调用底层图形库流程变得更简单，无需写一大堆代码只为绘制一个三角形；</li>
<li>SDL3 是跨平台的，这样移植到其他平台上的开发工作就可以大大简化；</li>
<li>SDL3 免费开源，无需担心哪一天会突然冒出个什么什么 Fee🤣；</li>
</ol>
<p>本系列将以我的视角为主，Roy 作为联合作者来记录从零开始的开发中学习到的各种技术，以及自己的理解。以 SDL3 为主，同时也会包含 Godot；除此之外，还会有游戏的一些构想、开发时的一些幕后花絮等等。由于游戏引擎的内容涉及到方方面面，很难完全做到面面俱到。因此若有遗疏错漏还请见谅。</p>
</body></html>
						]]>
					</content:encoded>
				</item>
			

				<item>
					<title>驱动 AY-3-8910（一）</title>
					<link>https://studiountagged.top/articles/2025-09-28-AY-3-8910</link>
					<guid isPermaLink="true">https://studiountagged.top/articles/2025-09-28-AY-3-8910/</guid>
					<pubDate>Sat, 27 Sep 2025 16:00:00 GMT</pubDate>
					<author>Steven Liu</author>
					
					<description>
						<![CDATA[
							通过 Arduino 驱动 AY-3-8910。
Driving AY-3-8910 by Arduino.
							
						]]>
					</description>
					<content:encoded>
						<![CDATA[
							<html><head></head><body><h2 id="前言-foreword" tabindex="-1">前言 Foreword</h2>
<p>早在计算机技术不甚发达的时候，想让电脑播放音频或者音乐是件很困难的事情。因为当时的数字采样技术技术并不成熟。如果追究音质，采样后的文件体积将变得十分庞大。这对于那个「寸金难买寸内存」的年代来说，直接在软件里使用简直是不可能的事情。最开始可能只有蜂鸣器发出的单调的滴滴答答。直到后来，才出现了只保存乐谱，剩下发出声音的工作交给电路的 Tracker Music 和 MIDI。这也就是电子音乐以及各种效果器、合成器等的起点。</p>
<p><a href="https://en.wikipedia.org/wiki/General_Instrument_AY-3-8910" class="external-link" target="_blank">AY-3-8910</a> 是一款由<a href="https://en.wikipedia.org/wiki/General_Instrument" class="external-link" target="_blank">通用仪器</a> (General Instrument, GI) 公司生产的 8-bit 可编程声音发生器 (Programmable Sound Generator, PSG)，内置三个通道，也就是说可以同时播放三种声音，同时还有一个噪声发生器以及一个<a href="https://en.wikipedia.org/wiki/Envelope_(music)" class="external-link" target="_blank">包络</a>发生器。在上个世纪 80 年代，广泛用于各种街机，例如 KONAMI 的 Gyruss；以及家用电脑，如 ZX Spectrum 中。<sup class="footnote-ref"><a href="#footnote1">[1]</a><a class="footnote-anchor" id="footnote-ref1"></a></sup></p>
<p>这个项目是以 <a href="https://github.com/internalregister/AY-3-8910/tree/master" class="external-link" target="_blank">InternalRegister</a> 的项目为基础改编而成。AY-3-8910 的手册请参见<a href="https://f.rdw.se/AY-3-8910-datasheet.pdf" class="external-link" target="_blank">此链接</a>。</p>
<h2 id="硬件准备-hardware-preparation" tabindex="-1">硬件准备 Hardware Preparation</h2>
<p>AY-3-8910 的元件引脚及功能如下表：</p>
<div class="table-container"><table>
<thead>
<tr>
<th style="text-align:center"><span>引脚</span><span>Pins</span></th>
<th style="text-align:center"><span>符号</span><span>Symbol</span></th>
<th style="text-align:center"><span>输入输出</span><span>IO</span></th>
<th style="text-align:center"><span>电平</span><span>Level</span></th>
<th><span>描述</span><span>Description</span></th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center"><code>1</code></td>
<td style="text-align:center"><code>VSS</code></td>
<td style="text-align:center">-</td>
<td style="text-align:center">0V</td>
<td>接地</td>
</tr>
<tr>
<td style="text-align:center"><code>2</code></td>
<td style="text-align:center"><code>NC</code></td>
<td style="text-align:center">-</td>
<td style="text-align:center">-</td>
<td>悬空</td>
</tr>
<tr>
<td style="text-align:center"><code>3</code></td>
<td style="text-align:center"><code>ANALOG CHANNEL B</code></td>
<td style="text-align:center">O</td>
<td style="text-align:center">-</td>
<td>模拟信号输出通道 B</td>
</tr>
<tr>
<td style="text-align:center"><code>4</code></td>
<td style="text-align:center"><code>ANALOG CHANNEL A</code></td>
<td style="text-align:center">O</td>
<td style="text-align:center">-</td>
<td>模拟信号输出通道 A</td>
</tr>
<tr>
<td style="text-align:center"><code>5</code></td>
<td style="text-align:center"><code>NC</code></td>
<td style="text-align:center">-</td>
<td style="text-align:center">-</td>
<td>悬空</td>
</tr>
<tr>
<td style="text-align:center"><code>6-13</code></td>
<td style="text-align:center"><code>IOB7-IOB0</code></td>
<td style="text-align:center">I / O</td>
<td style="text-align:center">L / H</td>
<td>IO 接口 A</td>
</tr>
<tr>
<td style="text-align:center"><code>14-21</code></td>
<td style="text-align:center"><code>IOA7-IOA0</code></td>
<td style="text-align:center">I / O</td>
<td style="text-align:center">L / H</td>
<td>IO 接口 B</td>
</tr>
<tr>
<td style="text-align:center"><code>22</code></td>
<td style="text-align:center"><code>CLOCK</code></td>
<td style="text-align:center">I</td>
<td style="text-align:center">-</td>
<td>时钟信号</td>
</tr>
<tr>
<td style="text-align:center"><code>23</code></td>
<td style="text-align:center"><code style="text-decoration: overline">RESET</code></td>
<td style="text-align:center">I</td>
<td style="text-align:center">L</td>
<td>重置，低电平有效</td>
</tr>
<tr>
<td style="text-align:center"><code>24</code></td>
<td style="text-align:center"><code>A9</code></td>
<td style="text-align:center">I</td>
<td style="text-align:center">L / H</td>
<td>地址 9</td>
</tr>
<tr>
<td style="text-align:center"><code>25</code></td>
<td style="text-align:center"><code>A8</code></td>
<td style="text-align:center">I</td>
<td style="text-align:center">L / H</td>
<td>地址 8</td>
</tr>
<tr>
<td style="text-align:center"><code>26, 39</code></td>
<td style="text-align:center"><code>TEST 2, TEST 1</code></td>
<td style="text-align:center">-</td>
<td style="text-align:center">-</td>
<td>测试用引脚，悬空</td>
</tr>
<tr>
<td style="text-align:center"><code>27</code></td>
<td style="text-align:center"><code>BDIR</code></td>
<td style="text-align:center">I</td>
<td style="text-align:center">L / H</td>
<td>总线方向</td>
</tr>
<tr>
<td style="text-align:center"><code>28</code></td>
<td style="text-align:center"><code>BC2</code></td>
<td style="text-align:center">I</td>
<td style="text-align:center">L / H</td>
<td>总线控制 2</td>
</tr>
<tr>
<td style="text-align:center"><code>29</code></td>
<td style="text-align:center"><code>BC1</code></td>
<td style="text-align:center">I</td>
<td style="text-align:center">L / H</td>
<td>总线控制 1</td>
</tr>
<tr>
<td style="text-align:center"><code>30-37</code></td>
<td style="text-align:center"><code>DA7-DA0</code></td>
<td style="text-align:center">I / O / 高阻抗</td>
<td style="text-align:center">L / H</td>
<td>数据 / 地址总线 0-7</td>
</tr>
<tr>
<td style="text-align:center"><code>38</code></td>
<td style="text-align:center"><code>ANALOG CHANNEL C</code></td>
<td style="text-align:center">O</td>
<td style="text-align:center">-</td>
<td>模拟信号输出通道 C</td>
</tr>
<tr>
<td style="text-align:center"><code>40</code></td>
<td style="text-align:center"><code>VCC</code></td>
<td style="text-align:center">-</td>
<td style="text-align:center">+5V</td>
<td>+5V 电源</td>
</tr>
</tbody>
</table>
</div><p>一共 40 个引脚，看起来貌似相当复杂，有点不知从何下手。但是仔细观察就会发现，IO 接口占了很大一部分。这些接口是用来与外部设备通信的，并不会影响到音频的功能，一般也很少用到。这也就是为什么这款芯片的亲戚 AY-3-8912 砍掉了一半的 IO 接口。本项目中不会有这种需求，因此全部悬空即可。除此之外，还有几个引脚标注了<code>NC</code>和<code>TEST</code>，这些也可以直接悬空。</p>
<h3 id="输入引脚-input-pins" tabindex="-1">输入引脚 Input Pins</h3>
<p>剩下的引脚才是真正要用到的。电源、接地和 8 位的数据 / 地址总线自不用说。先来看用于输入的引脚。第 22 号引脚<code>CLOCK</code>是<strong>时钟信号输入</strong>。芯片若要发生波形，就必须借助时钟信号这种有周期性的方波信号来完成控制时序、调制波形等操作。时钟信号可以通过<a href="https://en.wikipedia.org/wiki/Crystal_oscillator" class="external-link" target="_blank">晶振</a>来提供；不过在本项目中则会使用 Arduino 来生成。具体的生成方法将在本文后面进行详细介绍。</p>
<p>第 23 号引脚<code>RESET</code>是<strong>复位信号输入</strong>，低电平有效。复位时会将芯片所有的寄存器置 0。</p>
<p>第 24 和 25 号引脚<code>A9</code>, <code>A8</code>是地址线。这两个地址线的作用是什么呢？<code>DA0-DA7</code>这 8 个引脚既可以作输入数据，也可以输入地址。当作为地址线使用的时候，最多可以被映射成 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mn>2</mn><mn>8</mn></msup><mo>=</mo><mn>256</mn></mrow><annotation encoding="application/x-tex">2^{8} = 256</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8141em;"></span><span class="mord"><span class="mord">2</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">8</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">256</span></span></span></span> 个地址。而如果再加上这两根额外的地址线，就可以映射成 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mn>2</mn><mn>10</mn></msup><mo>=</mo><mn>1024</mn></mrow><annotation encoding="application/x-tex">2^{10} = 1024</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8141em;"></span><span class="mord"><span class="mord">2</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">10</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1024</span></span></span></span> 个地址。这个在有很多 IO 设备，或者同时使用很多块这种芯片时可以扩展地址的识别范围。简单来说，就是选择要用的芯片。不过说了这么多，本项目其实并不会用上。因此只需将<code>A9</code>接地，<code>A8</code>接电源即可。</p>
<p>接下来的第 27，28，29 号引脚<code>BDIR</code>, <code>BC2</code>, <code>BC1</code>引脚用于控制总线。虽然三个引脚一共有 8 种电平的组合，手册给出了一个简化的版本，即保持<code>BC2</code>为高电平，剩下的就交给<code>BDIR</code>和<code>BC1</code>控制。芯片生成的音调、音量以及噪声和包络发生都要通过向寄存器内写入数据才能控制，而在这之前还得先选择锁存器的地址。这一点在后面也会详细介绍。</p>
<h3 id="输出引脚-output-pins" tabindex="-1">输出引脚 Output Pins</h3>
<p>OK，输入的部分介绍完了，接下来就是输出的部分。AY-3-8910 提供了三个通道输出，分别为第 3，4 以及 38 号引脚的 B，A，C 模拟输出。这些引脚内置了数模转换器，因此可以不需要再外接其他的转换电路。输出的功率可以直接带动入耳式的耳机，不过可以加个简单的滤波或者功放模块。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-09-28-AY-3-8910/img/AY-3-8910.avif" alt="在 Proteus 中绘制的电路图。输出部分的滤波电路基于 GadgetReboot 的设计。虽然图中用的 PSG 芯片标注的是 YM2149，和 AY-3-8910 相比，唯一的区别就在于第26号引脚变成了SEL。不过根据手册上的描述，这个引脚也可以直接悬空。因此这两个芯片是完全兼容的。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>在 Proteus 中绘制的电路图。输出部分的滤波电路基于 <a href="https://github.com/GadgetReboot/AY-3-8910/tree/main" class="external-link" target="_blank">GadgetReboot</a> 的设计。虽然图中用的 PSG 芯片标注的是 YM2149，和 AY-3-8910 相比，唯一的区别就在于第<code>26</code>号引脚变成了<code>SEL</code>。不过根据手册上的描述，这个引脚也可以直接悬空。因此这两个芯片是完全兼容的。</span>
		</figcaption>
	
</figure><p></p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-09-28-AY-3-8910/img/Connection.avif" alt="实物连接图。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>实物连接图。</span>
		</figcaption>
	
</figure><p></p>
<h2 id="程序编写-programming" tabindex="-1">程序编写 Programming</h2>
<h3 id="生成时钟信号-generating-clock-signal" tabindex="-1">生成时钟信号 Generating Clock Signal</h3>
<p>笔者使用的 Arduino Mega 2560 采用的是基于 AVR 内核的 ATmega2560 处理器。在 AVR 内核中，一共有 3 个计时器，两个 8-bit (Timer 0 和 Timer 2)，以及一个 16-bit (Timer 1) 分辨率的计时器，用于实现与时间有关的操作时，如生成时钟信号、定时中断、频率测量以及 PWM 等。如果要生成 2 MHz 的时钟信号，基本的思路是这样的：Arduino Mega 2560 的时钟频率为 16 MHz，通过设置内部的计数器，让它数到某个数时执行翻转输出引脚上的电平的操作，同时清零为下半个周期做准备。而在这个过程中，将用到如下寄存器：</p>
<div class="table-container"><table>
<thead>
<tr>
<th style="text-align:center"><span>寄存器</span><span>Register</span></th>
<th><span>功能</span><span>Function</span></th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center"><code>TCNT</code></td>
<td>计时器 / 计数器寄存器Timer / Counter Register</td>
</tr>
<tr>
<td style="text-align:center"><code>TCCR</code></td>
<td>计时器 / 计数器控制寄存器Timer / Counter Control Register</td>
</tr>
<tr>
<td style="text-align:center"><code>OCR</code></td>
<td>输出比较寄存器Output Compare Register</td>
</tr>
</tbody>
</table>
</div><p>其中用来进行计时或计数的就是<code>TCNT1</code>，而控制计数器工作模式的就是<code>TCCR1A</code>和<code>TCCR1B</code>。<code>OCR1A</code>就是设置计数器从 0 开始要数到的目标数。<code>TCCR1A</code>和<code>TCCR1B</code>是怎么配置计数器的工作模式呢？这两个寄存器一共有 8 个控制位，每个控制位的功能如下：</p>
<div class="table-container"><table>
<thead>
<tr>
<th style="text-align:center"><span>位</span><span>Bit</span></th>
<th style="text-align:center"><span>TCCR1A</span></th>
<th><span>描述</span><span>Description</span></th>
<th style="text-align:center"><span>TCCR1B</span></th>
<th><span>描述</span><span>Description</span></th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center"><code>7</code></td>
<td style="text-align:center"><code>COM1A1</code></td>
<td>Compare Output Mode for Channel A</td>
<td style="text-align:center"><code>ICNC1</code></td>
<td>Input Capture Noise Canceler</td>
</tr>
<tr>
<td style="text-align:center"><code>6</code></td>
<td style="text-align:center"><code>COM1A0</code></td>
<td></td>
<td style="text-align:center"><code>ICES</code></td>
<td>Input Capture Edge Select</td>
</tr>
<tr>
<td style="text-align:center"><code>5</code></td>
<td style="text-align:center"><code>COM1B1</code></td>
<td>Compare Output Mode for Channel B</td>
<td style="text-align:center">-</td>
<td></td>
</tr>
<tr>
<td style="text-align:center"><code>4</code></td>
<td style="text-align:center"><code>COM1B0</code></td>
<td></td>
<td style="text-align:center"><code>WGM13</code></td>
<td></td>
</tr>
<tr>
<td style="text-align:center"><code>3</code></td>
<td style="text-align:center"><code>COM1C1</code></td>
<td>Compare Output Mode for Channel C</td>
<td style="text-align:center"><code>WGM12</code></td>
<td></td>
</tr>
<tr>
<td style="text-align:center"><code>2</code></td>
<td style="text-align:center"><code>COM1C0</code></td>
<td></td>
<td style="text-align:center"><code>CS12</code></td>
<td>Clock Select</td>
</tr>
<tr>
<td style="text-align:center"><code>1</code></td>
<td style="text-align:center"><code>WGM11</code></td>
<td>Wave Generation Mode</td>
<td style="text-align:center"><code>CS11</code></td>
<td></td>
</tr>
<tr>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>WGM10</code></td>
<td></td>
<td style="text-align:center"><code>CS10</code></td>
<td></td>
</tr>
</tbody>
</table>
</div><p>我们首先来看<code>WGM10</code>到<code>WGM13</code>这 4 位。这是用来控制<strong>波形生成模式</strong>的，一共有 16 种模式，其中大部分都是 PWM。在前文中提到过，计时的本质就是一个累加的计数器，因此我们要选择的模式是 CTC (Clear Timer on Compare Match) 模式，即计数器的值与目标数匹配时清零，重新开始。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-09-28-AY-3-8910/img/WGM.avif" alt="波形生成模式控制位描述。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>波形生成模式控制位描述。</span>
		</figcaption>
	
</figure><p></p>
<p>诸位读者可能注意到了，表格中的模式 4 和 12 的行为都是 CTC，而唯一不同的地方在于 TOP 那一列。模式 4 为<code>OCRnA</code>，模式 12 为<code>ICRn</code>。这两个有什么区别呢？简单来说，TOP 的意思就是计数器的终点值。<code>OCR</code>是<strong>输出比较寄存器 (Output Compare Register)</strong>。这个寄存器会被写入一个特定的值来指定计数的终点；而<code>ICR</code>是<strong>输入捕获寄存器 (Input Capture Register)</strong>。当外部的输入信号发生变化时，这个寄存器会立马记录下此时计数器的值作为计数的终点。打个比方，<code>OCR</code>就像是计时器，按照人为设定好的时间来周期性地计时；而<code>ICR</code>则相当于秒表，测量外部事件发生的时间。时钟信号是一种周期性信号，因此应该选择模式 4，将<code>WGM12</code>这一位设置为<code>1</code>，其他的<code>WGM</code>位设为<code>0</code>。</p>
<p>接着再来看看<code>COM1A1</code>和<code>COM1A0</code>这两位。这两位用于<strong>比较输出控制</strong>，控制当计数器数到终点时，对应的输出引脚该怎么办。在比较输出模式，非 PWM的情况下，手册上给出了如下几种输出行为：</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-09-28-AY-3-8910/img/Compare_Output_Mode.avif" alt="比较输出模式，非 PWM。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>比较输出模式，非 PWM。</span>
		</figcaption>
	
</figure><p></p>
<p>当<code>COM1A1 = 0, COM1A0 = 1</code>时，输出行为就是当匹配时翻转<code>OC1A / OC1B / OC1C</code>上的电平，这正是生成方波的关键。</p>
<p><code>CS10-CS12</code>这 3 位用于选择时钟，里面预设了很多档位的分频 (prescaler)。如果以处理器 16 MHz 的时钟信号为基准，虽然在 8 分频的情况下正好是 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>16</mn><mo>÷</mo><mn>8</mn><mo>=</mo><mn>2</mn><mrow><mi mathvariant="normal">M</mi><mi mathvariant="normal">H</mi><mi mathvariant="normal">z</mi></mrow></mrow><annotation encoding="application/x-tex">16 \div 8 = 2 \mathrm{MHz}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7278em;vertical-align:-0.0833em;"></span><span class="mord">16</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">÷</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">8</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord">2</span><span class="mord"><span class="mord mathrm">MHz</span></span></span></span></span>，不过由于真正的输出频率还得取决于计数器，因此不分频，将<code>CS10</code>设为<code>1</code>，其余位为<code>0</code>。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-09-28-AY-3-8910/img/Clock_Select.avif" alt="时钟选择位描述。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>时钟选择位描述。</span>
		</figcaption>
	
</figure><p></p>
<p>时钟信号将在<code>OC1A</code>引脚上输出。根据 Arduino 的引脚图，<code>D11</code>引脚的<span style="color: #00959C">青色</span>背景文字标注着<code>OCA1</code>。请注意，虽然基于 AVR 核心的处理器，例如 ATmega328P 等，也有相同的用于计时的寄存器，不过对于引脚的映射会略有不同。如果使用的是其他型号的开发板，请务必确认引脚的映射关系。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-09-28-AY-3-8910/img/Arduino_Mega_2560_Pinout_1.avif" alt="Arduino Mega 2560 的引脚图。请注意青色背景与橘红色条纹背景标注之间的关系。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>Arduino Mega 2560 的引脚图。请注意青色背景与橘红色条纹背景标注之间的关系。</span>
		</figcaption>
	
</figure><p></p>
<p>最后一步就是设定计数器的终点<code>OCR1</code>的值。这个值该怎么计算呢？诸位读者不妨跟着下面的过程推导一下。首先，每个脉冲的时长为：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>T</mi><mrow><mi>t</mi><mi>i</mi><mi>c</mi><mi>k</mi></mrow></msub><mo>=</mo><mfrac><mn>1</mn><msub><mi>f</mi><mrow><mi>C</mi><mi>L</mi><mi>K</mi></mrow></msub></mfrac></mrow><annotation encoding="application/x-tex">T_{tick} = \frac{1}{f_{CLK}}
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1389em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">t</span><span class="mord mathnormal mtight">i</span><span class="mord mathnormal mtight">c</span><span class="mord mathnormal mtight" style="margin-right:0.0315em;">k</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.2019em;vertical-align:-0.8804em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3214em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.0715em;">C</span><span class="mord mathnormal mtight">L</span><span class="mord mathnormal mtight" style="margin-right:0.0715em;">K</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.8804em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p>
<p>由于 Timer 1 是从 0 开始计数的，所以总共<code>OCR1A + 1</code>个计数周期。每次<code>OC1A</code>翻转所需时间：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>T</mi><mrow><mi>t</mi><mi>o</mi><mi>g</mi><mi>g</mi><mi>l</mi><mi>e</mi></mrow></msub><mo>=</mo><mo stretchy="false">(</mo><mrow><mi mathvariant="normal">O</mi><mi mathvariant="normal">C</mi><mi mathvariant="normal">R</mi><mn>1</mn><mi mathvariant="normal">A</mi></mrow><mo>+</mo><mn>1</mn><mo stretchy="false">)</mo><mo>⋅</mo><msub><mi>T</mi><mrow><mi>t</mi><mi>i</mi><mi>c</mi><mi>k</mi></mrow></msub><mo>=</mo><mfrac><mrow><mrow><mi mathvariant="normal">O</mi><mi mathvariant="normal">C</mi><mi mathvariant="normal">R</mi><mn>1</mn><mi mathvariant="normal">A</mi></mrow><mo>+</mo><mn>1</mn></mrow><msub><mi>f</mi><mrow><mi>C</mi><mi>L</mi><mi>K</mi></mrow></msub></mfrac></mrow><annotation encoding="application/x-tex">T_{toggle} = (\mathrm{OCR1A} + 1) \cdot T_{tick} = \frac{\mathrm{OCR1A} + 1}{ f_{CLK} }
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.9694em;vertical-align:-0.2861em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1389em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">t</span><span class="mord mathnormal mtight">o</span><span class="mord mathnormal mtight" style="margin-right:0.0359em;">g</span><span class="mord mathnormal mtight" style="margin-right:0.0359em;">g</span><span class="mord mathnormal mtight" style="margin-right:0.0197em;">l</span><span class="mord mathnormal mtight">e</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord"><span class="mord mathrm">OCR1A</span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1389em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">t</span><span class="mord mathnormal mtight">i</span><span class="mord mathnormal mtight">c</span><span class="mord mathnormal mtight" style="margin-right:0.0315em;">k</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.2408em;vertical-align:-0.8804em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3603em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.0715em;">C</span><span class="mord mathnormal mtight">L</span><span class="mord mathnormal mtight" style="margin-right:0.0715em;">K</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathrm">OCR1A</span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.8804em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p>
<p>而一个完整的方波周期需要两次翻转，意味着需要经历两个计数周期：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>T</mi><mrow><mi>p</mi><mi>e</mi><mi>r</mi><mi>i</mi><mi>o</mi><mi>d</mi></mrow></msub><mo>=</mo><mn>2</mn><mo>⋅</mo><msub><mi>T</mi><mrow><mi>t</mi><mi>o</mi><mi>g</mi><mi>g</mi><mi>l</mi><mi>e</mi></mrow></msub><mo>=</mo><mn>2</mn><mo>⋅</mo><mo stretchy="false">(</mo><mrow><mi mathvariant="normal">O</mi><mi mathvariant="normal">C</mi><mi mathvariant="normal">R</mi><mn>1</mn><mi mathvariant="normal">A</mi></mrow><mo>+</mo><mn>1</mn><mo stretchy="false">)</mo><mo>⋅</mo><mfrac><mn>1</mn><msub><mi>f</mi><mrow><mi>C</mi><mi>L</mi><mi>K</mi></mrow></msub></mfrac></mrow><annotation encoding="application/x-tex">T_{period} = 2 \cdot T_{toggle} = 2 \cdot (\mathrm{OCR1A} + 1) \cdot \frac{1}{f_{CLK}}
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.9694em;vertical-align:-0.2861em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1389em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">p</span><span class="mord mathnormal mtight" style="margin-right:0.0278em;">er</span><span class="mord mathnormal mtight">i</span><span class="mord mathnormal mtight">o</span><span class="mord mathnormal mtight">d</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">2</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.9694em;vertical-align:-0.2861em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1389em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">t</span><span class="mord mathnormal mtight">o</span><span class="mord mathnormal mtight" style="margin-right:0.0359em;">g</span><span class="mord mathnormal mtight" style="margin-right:0.0359em;">g</span><span class="mord mathnormal mtight" style="margin-right:0.0197em;">l</span><span class="mord mathnormal mtight">e</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">2</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord"><span class="mord mathrm">OCR1A</span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:2.2019em;vertical-align:-0.8804em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3214em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.0715em;">C</span><span class="mord mathnormal mtight">L</span><span class="mord mathnormal mtight" style="margin-right:0.0715em;">K</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.8804em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p>
<p>最终输出公式：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>f</mi><mrow><mi>o</mi><mi>u</mi><mi>t</mi></mrow></msub><mo>=</mo><mfrac><mn>1</mn><msub><mi>T</mi><mrow><mi>p</mi><mi>e</mi><mi>r</mi><mi>i</mi><mi>o</mi><mi>d</mi></mrow></msub></mfrac><mo>=</mo><mfrac><msub><mi>f</mi><mrow><mi>C</mi><mi>L</mi><mi>K</mi></mrow></msub><mrow><mn>2</mn><mo>⋅</mo><mo stretchy="false">(</mo><mrow><mi mathvariant="normal">O</mi><mi mathvariant="normal">C</mi><mi mathvariant="normal">R</mi><mn>1</mn><mi mathvariant="normal">A</mi></mrow><mo>+</mo><mn>1</mn><mo stretchy="false">)</mo></mrow></mfrac></mrow><annotation encoding="application/x-tex">f_{out} = \frac{1}{T_{period}} = \frac{f_{CLK}}{2 \cdot (\mathrm{OCR1A} + 1)}
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2806em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">o</span><span class="mord mathnormal mtight">u</span><span class="mord mathnormal mtight">t</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.2935em;vertical-align:-0.9721em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3214em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.1389em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">p</span><span class="mord mathnormal mtight" style="margin-right:0.0278em;">er</span><span class="mord mathnormal mtight">i</span><span class="mord mathnormal mtight">o</span><span class="mord mathnormal mtight">d</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.9721em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.3074em;vertical-align:-0.936em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3714em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mopen">(</span><span class="mord"><span class="mord mathrm">OCR1A</span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord">1</span><span class="mclose">)</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.0715em;">C</span><span class="mord mathnormal mtight">L</span><span class="mord mathnormal mtight" style="margin-right:0.0715em;">K</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.936em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p>
<p>当 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>f</mi><mrow><mi>o</mi><mi>u</mi><mi>t</mi></mrow></msub><mo>=</mo><mn>2</mn><mtext>&nbsp;</mtext><mrow><mi mathvariant="normal">M</mi><mi mathvariant="normal">H</mi><mi mathvariant="normal">z</mi></mrow><mo separator="true">,</mo><mtext>&nbsp;</mtext><msub><mi>f</mi><mrow><mi>C</mi><mi>L</mi><mi>K</mi></mrow></msub><mo>=</mo><mn>16</mn><mtext>&nbsp;</mtext><mrow><mi mathvariant="normal">M</mi><mi mathvariant="normal">H</mi><mi mathvariant="normal">z</mi></mrow></mrow><annotation encoding="application/x-tex">f_{out} = 2 \ \mathrm{MHz}, \ f_{CLK} = 16 \ \mathrm{MHz}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2806em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">o</span><span class="mord mathnormal mtight">u</span><span class="mord mathnormal mtight">t</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord">2</span><span class="mspace">&nbsp;</span><span class="mord"><span class="mord mathrm">MHz</span></span><span class="mpunct">,</span><span class="mspace">&nbsp;</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.0715em;">C</span><span class="mord mathnormal mtight">L</span><span class="mord mathnormal mtight" style="margin-right:0.0715em;">K</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord">16</span><span class="mspace">&nbsp;</span><span class="mord"><span class="mord mathrm">MHz</span></span></span></span></span> 时，代入可解得 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi mathvariant="normal">O</mi><mi mathvariant="normal">C</mi><mi mathvariant="normal">R</mi><mn>1</mn><mi mathvariant="normal">A</mi></mrow><mo>=</mo><mn>3</mn></mrow><annotation encoding="application/x-tex">\mathrm{OCR1A} = 3</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord"><span class="mord mathrm">OCR1A</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">3</span></span></span></span>。</p>
<p>其实说了这么多，要写的代码也就五行。因为这些涉及到了相当底层的操作，笔者也是边学边写，因此会尽可能记录得详细一点。</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C++</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C++"><span class="hljs-meta">#<span class="hljs-keyword">define</span> CLOCKOUT 11</span>

TCCR1A = <span class="hljs-built_in">bit</span>(COM1A0);
TCCR1B = <span class="hljs-built_in">bit</span>(WGM12) | <span class="hljs-built_in">bit</span>(CS10);
OCR1A = <span class="hljs-number">3</span>;
<span class="hljs-built_in">pinMode</span>(CLOCKOUT, OUTPUT);
</code></pre>
	</div>
</div>

<h3 id="驱动芯片-driving-the-chip" tabindex="-1">驱动芯片 Driving the Chip</h3>
<p>能够生成稳定的时钟信号以后，接下来就是驱动这块芯片了。这块芯片的各种功能都是通过写寄存器实现的。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-09-28-AY-3-8910/img/PSG_Register_Array.avif" alt="芯片的寄存器阵列。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>芯片的寄存器阵列。</span>
		</figcaption>
	
</figure><p></p>
<p>由于这块芯片是寄存器地址和数据共用，因此得先通过<code>BDIR</code>和<code>BC1</code>来指定总线的读写方向：</p>
<div class="table-container"><table>
<thead>
<tr>
<th><code>BDIR</code></th>
<th><code>BC2</code></th>
<th><code>BC1</code></th>
<th><span>PSG 功能</span><span>PSG Function</span></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>0</code></td>
<td><code>1</code></td>
<td><code>0</code></td>
<td>非活动 INACTIVE</td>
</tr>
<tr>
<td><code>0</code></td>
<td><code>1</code></td>
<td><code>1</code></td>
<td>从 PSG 读取 READ FROM PSG</td>
</tr>
<tr>
<td><code>1</code></td>
<td><code>1</code></td>
<td><code>0</code></td>
<td>向 PSG 写入 WRITE TO PSG</td>
</tr>
<tr>
<td><code>1</code></td>
<td><code>1</code></td>
<td><code>1</code></td>
<td>设置寄存器地址 LATCH ADDRESS</td>
</tr>
</tbody>
</table>
</div><p>那么，首先定义一个函数<code>SetPsgMode()</code>来切换模式：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C++</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C++"><span class="hljs-keyword">enum</span> <span class="hljs-title class_">PSGMode</span> { INACTIVE, READ, WRITE, LATCH };

<span class="hljs-comment">// Set the PSG function</span>
<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">SetPsgMode</span><span class="hljs-params">(<span class="hljs-keyword">enum</span> PSGMode mode)</span> </span>{
&nbsp; &nbsp; <span class="hljs-keyword">switch</span> (mode) {
&nbsp; &nbsp; &nbsp; &nbsp; <span class="hljs-keyword">case</span> INACTIVE:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="hljs-built_in">digitalWrite</span>(BC1, LOW);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="hljs-built_in">digitalWrite</span>(BDIR, LOW);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="hljs-keyword">break</span>;

&nbsp; &nbsp; &nbsp; &nbsp; <span class="hljs-keyword">case</span> READ:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="hljs-built_in">digitalWrite</span>(BC1, HIGH);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="hljs-built_in">digitalWrite</span>(BDIR, LOW);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="hljs-keyword">break</span>;

&nbsp; &nbsp; &nbsp; &nbsp; <span class="hljs-keyword">case</span> WRITE:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="hljs-built_in">digitalWrite</span>(BC1, LOW);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="hljs-built_in">digitalWrite</span>(BDIR, HIGH);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="hljs-keyword">break</span>;

&nbsp; &nbsp; &nbsp; &nbsp; <span class="hljs-keyword">case</span> LATCH:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="hljs-built_in">digitalWrite</span>(BC1, HIGH);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="hljs-built_in">digitalWrite</span>(BDIR, HIGH);
&nbsp; &nbsp; }
}
</code></pre>
	</div>
</div>

<p>按照手册上的描述，写入数据的流程应该是这样的：非活动 → 写入地址 → 非活动 → 写入数据 → 非活动。而<code>DA0-DA7</code>上写入数据的脉冲宽度应在 500 到 10,000 ns 之间。由此就构成了用于写入的函数<code>WriteByte()</code>：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C++</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C++"><span class="hljs-comment">// Write data into the specified register</span>
<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">WriteByte</span><span class="hljs-params">(<span class="hljs-type">uint8_t</span> reg, <span class="hljs-type">uint8_t</span> data)</span> </span>{
&nbsp; &nbsp; <span class="hljs-built_in">SetPsgMode</span>(LATCH);
&nbsp; &nbsp; PORTF = reg;
&nbsp; &nbsp; <span class="hljs-built_in">delayMicroseconds</span>(<span class="hljs-number">10</span>);
&nbsp; &nbsp; <span class="hljs-built_in">SetPsgMode</span>(INACTIVE);

&nbsp; &nbsp; <span class="hljs-built_in">SetPsgMode</span>(WRITE);
&nbsp; &nbsp; PORTF = data;
&nbsp; &nbsp; <span class="hljs-built_in">delayMicroseconds</span>(<span class="hljs-number">10</span>);
&nbsp; &nbsp; <span class="hljs-built_in">SetPsgMode</span>(INACTIVE);
}
</code></pre>
	</div>
</div>

<p>这块芯片有 A，B，C 三个通道，而每个通道可以独立开关，或者控制音调 (Tone，或周期 Tone Period)、音量 (Volume，或振幅 Amplitude) 以及包络 (Envelope) 形状、周期；同时，也可以选择一个独立的噪声发生器和其中一个通道混合在一起输出。在本篇文章中暂时不会用到噪声和包络，前者常用于模仿鼓点或者爆炸等音效，后者则会改变输出音符的效果等。目前的任务是能够成功驱动这块芯片，并播放出简单的音符，譬如在通道 A 上播放 C4-B4 这七个音。</p>
<p>先看通道 A 的输出是怎么控制的。首先，<code>R0</code>和<code>R1</code>这两个寄存器用来调整输出的音调。<code>R0</code>用 8 位来微调 (Fine Tune)，再加上<code>R1</code>的前 4 位粗调 (Coarse Tune)，总共 12 位。其实和处理器输出时钟信号的原理如出一辙，这里的这 12 位本质上也是个计数器。计数器将从给定的值开始倒数，直到倒数到零时翻转输出引脚上的电平，并重新开始。那么，要传入寄存器的值该如何计算呢？手册上已经很贴心地给出了公式：</p>
<p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>f</mi><mi>T</mi></msub><mo>=</mo><mfrac><msub><mi>f</mi><mrow><mi>C</mi><mi>L</mi><mi>K</mi></mrow></msub><mrow><mn>16</mn><mo>⋅</mo><mrow><mi mathvariant="normal">T</mi><mi mathvariant="normal">o</mi><mi mathvariant="normal">n</mi><mi mathvariant="normal">e</mi><mi mathvariant="normal">P</mi><mi mathvariant="normal">e</mi><mi mathvariant="normal">r</mi><mi mathvariant="normal">i</mi><mi mathvariant="normal">o</mi><mi mathvariant="normal">d</mi></mrow></mrow></mfrac></mrow><annotation encoding="application/x-tex">f_{T} = \frac{f_{CLK}}{16 \cdot \mathrm{TonePeriod}}
</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.1389em;">T</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.0574em;vertical-align:-0.686em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3714em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">16</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathrm">TonePeriod</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.0715em;">C</span><span class="mord mathnormal mtight">L</span><span class="mord mathnormal mtight" style="margin-right:0.0715em;">K</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p>
<p>其中，<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>f</mi><mrow><mi>C</mi><mi>L</mi><mi>K</mi></mrow></msub></mrow><annotation encoding="application/x-tex">f_{CLK}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.0715em;">C</span><span class="mord mathnormal mtight">L</span><span class="mord mathnormal mtight" style="margin-right:0.0715em;">K</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 是时钟频率。由于芯片内部还有个 16 分频器，所以要除以 16；<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>f</mi><mi>T</mi></msub></mrow><annotation encoding="application/x-tex">f_T</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.1076em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.1389em;">T</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 是要生成的频率，<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="normal">T</mi><mi mathvariant="normal">o</mi><mi mathvariant="normal">n</mi><mi mathvariant="normal">e</mi><mi mathvariant="normal">P</mi><mi mathvariant="normal">e</mi><mi mathvariant="normal">r</mi><mi mathvariant="normal">i</mi><mi mathvariant="normal">o</mi><mi mathvariant="normal">d</mi></mrow><annotation encoding="application/x-tex">\mathrm{TonePeriod}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord"><span class="mord mathrm">TonePeriod</span></span></span></span></span> 是要传入寄存器的值。例如，如果想生成 C4 (262 Hz) 这个音调的话，代入解得 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi mathvariant="normal">T</mi><mi mathvariant="normal">o</mi><mi mathvariant="normal">n</mi><mi mathvariant="normal">e</mi><mi mathvariant="normal">P</mi><mi mathvariant="normal">e</mi><mi mathvariant="normal">r</mi><mi mathvariant="normal">i</mi><mi mathvariant="normal">o</mi><mi mathvariant="normal">d</mi></mrow><mo>≈</mo><mn>477</mn></mrow><annotation encoding="application/x-tex">\mathrm{TonePeriod} \approx 477</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord"><span class="mord mathrm">TonePeriod</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">477</span></span></span></span>。</p>
<p>网上有很多地方给出了音名和频率的对照关系表，手册上也有基于 1.78977 MHz 时钟频率计算出的应该写入寄存器的值。基于此可以生成一个头文件专门储存音名和要写入寄存器的值的对应关系。这样的好处就是每次要用什么音符直接调用它的名字即可，不仅可读性更高，而且可以比储存音名和频率的对应关系这种方案快一些，因为不需要每次再去计算要写入的值。不过，如果使用了不同的频率，最终输出会略有移调(其实也并无大碍，因为我们听音乐本质上听的是音程关系而非频率)。</p>
<p>芯片是通过 12 位来控制音调的，低 8 位要写入<code>R0</code>，高 4 位要写入<code>R1</code>，因此可以先定义一个 16 位的变量<code>uint16_t tone</code>。而要取它的低 8 位和高 4 位，可以配合逻辑与和右移操作实现，最后再强制类型转为为 8 位的数据。而音量则通过<code>R10</code>寄存器来控制，其中 bit0 - bit4 为音量的值(0-15)，bit5 为静音。最后，再通过 <code>delay()</code>来控制音符播放的时间：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C++</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C++"><span class="hljs-comment">// Play the notes according to the specified parameters</span>
<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">PlayNoteEvent</span><span class="hljs-params">(<span class="hljs-type">int</span> duration, <span class="hljs-type">uint16_t</span> tone, <span class="hljs-type">uint8_t</span> volume)</span> </span>{
&nbsp; &nbsp; <span class="hljs-type">uint8_t</span> r0 = (<span class="hljs-type">uint8_t</span>) tone &amp; <span class="hljs-number">0xFF</span>; <span class="hljs-comment">// The value of Register 0</span>
&nbsp; &nbsp; <span class="hljs-type">uint8_t</span> r1 = (<span class="hljs-type">uint8_t</span>) (tone &gt;&gt; <span class="hljs-number">8</span>) &amp; <span class="hljs-number">0x0F</span>; <span class="hljs-comment">// The value of Register 1</span>

&nbsp; &nbsp; <span class="hljs-built_in">WriteByte</span>(<span class="hljs-number">0</span>, r0 &amp; <span class="hljs-number">0xFF</span>);
&nbsp; &nbsp; <span class="hljs-built_in">WriteByte</span>(<span class="hljs-number">1</span>, r1 &amp; <span class="hljs-number">0x0F</span>);
&nbsp; &nbsp; <span class="hljs-built_in">WriteByte</span>(<span class="hljs-number">8</span>, volume);

&nbsp; &nbsp; <span class="hljs-built_in">delay</span>(duration);
}
</code></pre>
	</div>
</div>

<p>初始化并启用通道 A 的输出。在<code>R7</code>寄存器中，bit0 就是控制通道 A 是否启用，低电平有效，因此将其设为<code>0</code>，其他设为<code>1</code>即可：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C++</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C++"><span class="hljs-built_in">WriteByte</span>(<span class="hljs-number">7</span>, <span class="hljs-number">0b00111110</span>); <span class="hljs-comment">// Enable channel A tone output</span>
</code></pre>
	</div>
</div>

<h3 id="拓展-封装类-encapsulating-classes" tabindex="-1">拓展：封装类 Encapsulating Classes</h3>
<p>在此前的文章<a href="/articles/2025-09-11-LCD12864-ST7920/">驱动 LCD12864 ST7920(一)</a>中，是将整个驱动过程，划分成一个个的小步骤完成的。这个就是<a href="https://en.m.wikipedia.org/wiki/Procedural_programming" class="external-link" target="_blank">面向过程编程 (Procedural Programming)</a>。这种编程方式的核心就是函数。例如，假设现在我们要完成「把大象放进冰箱里」，一共三步，即打开冰箱门、把大象放进去以及关闭冰箱门。在面向过程编程中，这三个步骤就会被封装成三个函数，然后由主函数依次调用。这种编程方式在早期的编程语言 例如 C，BASIC，Pascal 中尤为常见。</p>
<p>而在本项目中，将使用另外一种思维方式，即<a href="https://en.m.wikipedia.org/wiki/Object-oriented_programming" class="external-link" target="_blank">面向对象编程 (Object-Oriented Programming)</a>。比起前者的将一个问题拆分为很多很多的步骤，这种则是将问题的各种属性、方法等封装在一起，抽象成一个<strong>类 (Class)</strong>。例如，在使用 C# 开发的游戏中，如果要控制角色的行为，通常都会通过它们的类实例化成一个可供操作的<strong>对象 (Object)</strong>。这个对象里面将包含各种譬如生命值、攻击力、防御力等属性，以及移动、攻击、受伤等方法。这样，当角色移动或受伤时，只需通过调用这个对象里面的方法，就可以控制角色的坐标或生命值。</p>
<p>这种编程方式的好处就是在一些大型项目中可以选择只暴露必须的接口，避免了函数过多导致的混乱；而且类也可以被继承，便于扩展和复用，例如在 Unity 中，脚本都会默认继承自一个叫做<code>MonoBehaviour</code>的类，这个类里面就已经预先封装好了比如初始化、每帧调用等方法。这里我不会展开太多，不过以后应该也会有独立游戏开发笔记的文章。回到正题，现代有很多编程语言都是面向对象的，例如 C++，C# 等；这也是 C++ 和 C 的主要区别之一。</p>
<p>那么该如何将目前写的代码改为面向对象的形式呢？首先在头文件<code>ay38910.h</code>中通过<code>class</code>声明<code>AY38910Player</code>这个类。这个将用于描述所有播放器共有的属性和行为。类里面的各种变量或方法统称为<strong>成员 (Member)</strong>。</p>
<p>面向对象编程中有个很重要的概念就是<strong>动态成员 (Dynamic Member)</strong> 和 <strong>静态成员 (Static Member)</strong>。那么，这两个究竟是什么意思呢？举个例子，由于 AY-3-8910 这块芯片有三个通道，因此每个通道播放的音符、持续的时间、音量等会不一样。这些会变化的内容就被称为动态成员。而静态成员就指那些每个对象共享的、不依赖于对象的。例如默认的 BPM 就可以看作是一个静态成员变量；而设置 BPM 的方法就是一个静态成员函数。</p>
<p>在一个类里面，可以通过<code>public</code>和<code>private</code>来设置权限，选择哪些成员是可以公开访问，哪些只能由同一个类的成员访问。例如，假设我不希望暴露<code>WriteByte</code>这个方法：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C++</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C++"><span class="hljs-comment">// ay38910.h</span>

<span class="hljs-keyword">class</span> <span class="hljs-title class_">AY38910Player</span> {
	<span class="hljs-keyword">public</span>:
		<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">SetPsgMode</span><span class="hljs-params">(<span class="hljs-keyword">enum</span> PSGMode mode)</span></span>;
		<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">Initialize</span><span class="hljs-params">()</span></span>;
		<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">PlayNoteEvent</span><span class="hljs-params">(<span class="hljs-type">int</span> duration, <span class="hljs-type">uint16_t</span> tone, <span class="hljs-type">uint8_t</span> volume)</span></span>;
	
	<span class="hljs-keyword">private</span>:
		<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">WriteByte</span><span class="hljs-params">(<span class="hljs-type">uint8_t</span> reg, <span class="hljs-type">uint8_t</span> data)</span></span>;
}
</code></pre>
	</div>
</div>

<p>这样，<code>WriteByte()</code>就只能通过由公开的方法间接访问了，避免了因为写入了错误的数据而直接无法工作。</p>
<p>不过这还没完。如果要在外部实现类里面的成员函数，例如<code>ay38910.cpp</code>，则需要通过作用域解析运算符<code>::</code>，来明确它们所属的作用域。例如：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C++</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C++"><span class="hljs-comment">// ay38910.cpp</span>

<span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"ay38910.h"</span></span>
<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">AY38910Player::SetPsgMode</span><span class="hljs-params">(<span class="hljs-keyword">enum</span> PSGMode mode)</span> </span>{}
<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">AY38910Player::WriteByte</span><span class="hljs-params">(<span class="hljs-type">uint8_t</span> reg, <span class="hljs-type">uint8_t</span> data)</span> </span>{}
</code></pre>
	</div>
</div>

<p>目前，还只是定义了类以及实现方法。对于静态成员，可以直接通过<code>::</code>访问，比如<code>AY38910Player::defaultBpm</code>。而对于动态成员，则必须先<strong>实例化 (Instantiation)</strong> 成对象。打个比方，类就相当于一份蓝图，描述了某物的结构和行为，没有实际数据；而对象就是根据蓝图制造出来的机械或建筑，有实际功能，会在内存中分配空间储存成员变量。</p>
<p>一般实例化对象有两个方法。方法一：栈实例化。这是最简单、也最常用的方法。<strong>栈 (Stack)</strong> 是指程序运行时在内存中开辟的一小块固定的空间，用来取存一些局部和临时数据：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C++</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C++">AY38910Player player;
</code></pre>
	</div>
</div>

<p>现在，<code>player</code>就是一个<code>AY38910Player</code>对象。要访问这个对象里的成员，就可以通过成员访问运算符<code>.</code>：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C++</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C++">player.<span class="hljs-built_in">SetPsgMode</span>(INACTIVE);
</code></pre>
	</div>
</div>

<p>栈会自动管理内存分配，一般用于局部变量、函数参数、栈实例化的对象等。而如果有一个巨大的数据容器，比如储存整首曲子的信息时，就不推荐使用栈，因为它的空间有限，过大的数据可能会造成栈溢出(Stack Overflow)。因此，对于这样的数据，或者是生命周期更长的，要使用方法二来实例化。</p>
<p>方法二：堆实例化。与栈不同的是，<strong>堆 (Heap)</strong> 是一块更大一点的空间，由操作系统动态分配。使用的时候必须得手动通过<code>new</code>分配，以及最后用<code>delete</code>释放。在分配时，<code>new</code>操作符返回的是一个指向为这个对象开辟的内存空间的地址。这也是和栈实例化不同的一点，因为后者可以直接访问。这样，就需要用指针通过成员访问运算符<code>-&gt;</code>来调用对象里的方法：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C++</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C++">AY38910Player* playerPtr = <span class="hljs-keyword">new</span> <span class="hljs-built_in">AY38910Player</span>();
playerPtr -&gt; <span class="hljs-built_in">SetModePsg</span>(INACTIVE);
<span class="hljs-keyword">delete</span> playerPtr;
</code></pre>
	</div>
</div>

<p>那么，为什么堆需要手动释放内存呢？由于栈是位于一块固定的区域，而且里面的分配情况都会自动管理。例如在函数结束以后，临时数据等所占用的空间就会被自动释放；而通过<code>new</code>创建对象时，如果没有手动释放内存，那么程序在运行过程中就丢失了对这块堆内存的控制权，既不能被访问、也不能被重新分配给其他程序，而这就是<strong>内存泄漏 (Memory Leak)</strong>。所以，请务必记得在最后要释放内存。</p>
<p>不过，在 C++ 11 的标准中，提供了<strong>智能指针 (Smart Pointer)</strong><code>std::unique_ptr</code><sup class="footnote-ref"><a href="#footnote2">[2]</a><a class="footnote-anchor" id="footnote-ref2"></a></sup>，包含在头文件<code>&lt;memory&gt;</code>中。可以通过如下方式来创建：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C++</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C++">std::unique_ptr&lt;AY38910Player&gt; playerPtr = 
	std::<span class="hljs-built_in">make_unique</span>&lt;AY38910Player&gt;();
</code></pre>
	</div>
</div>

<p>通过这种方式封装的数据，其占用的内存将在函数销毁或超出作用域（即一对大括号包裹的区域）时自动释放。这是一个更好的 C++ 实践，因为它更安全，能防止某些异常场景下的内存泄漏，而且更高效。关于这点将在下一篇文章中进行更详细的介绍。</p>
<h2 id="后记-epilogue" tabindex="-1">后记 Epilogue</h2>
<p>为了驱动这块芯片，这个过程说不上一帆风顺，总会出些电路或者软件上的小毛病。光是调试就花费了不少时间。不过，当听到从扬声器里播放出预期的音符时的那种喜悦很快就将调试时的种种阴翳一扫而空。这个过程中也学到了很多很多的新知识。不仅仅是硬件和编程上的，关于如何采集芯片的输出信号、改编曲子、解析 Tracker Music 文件结构等，就涉及到了信号处理、乐理等内容了。在下篇文章中，我将会介绍如何制作一个简单的 Tracker Music 播放器。</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="footnote1" class="footnote-item"><p><a href="https://en.wikipedia.org/wiki/General_Instrument_AY-3-8910" class="external-link" target="_blank">General Instrument AY-3-8910 - Wikipedia</a> <a href="#footnote-ref1" class="footnote-backref">↩︎</a></p>
</li>
<li id="footnote2" class="footnote-item"><p><a href="https://cppreference.cn/w/cpp/memory/unique_ptr" class="external-link" target="_blank">std::unique_ptr - cppreference.cn - C++参考手册</a> <a href="#footnote-ref2" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
</body></html>
						]]>
					</content:encoded>
				</item>
			

				<item>
					<title>驱动 LCD12864 ST7920（一）</title>
					<link>https://studiountagged.top/articles/2025-09-11-LCD12864-ST7920</link>
					<guid isPermaLink="true">https://studiountagged.top/articles/2025-09-11-LCD12864-ST7920/</guid>
					<pubDate>Wed, 10 Sep 2025 16:00:00 GMT</pubDate>
					<author>Steven Liu</author>
					
					<description>
						<![CDATA[
							通过 Arduino 驱动 LCD12864 ST7920。
Driving LCD12864 ST7920 by Arduino.
							
						]]>
					</description>
					<content:encoded>
						<![CDATA[
							<html><head></head><body><h2 id="前言" tabindex="-1">前言</h2>
<p>继上次的成功驱动了 LCD1602 以后，这几天又入手了一块 LCD12864。之所以叫 LCD12864，是因为它的屏幕是由 128 * 64 大小的点阵构成的。本文章将以学习笔记加个人想法的形式，记录使用 Arduino Mega 2560 驱动它的过程，因此很多内容会写得相当细致。同样，为了更好地理解底层的工作原理，这个项目将不会借助譬如 U8glib/U8g2 这样的库，而是会通过自己编写控制 LCD12864 的各种命令和函数。</p>
<h2 id="硬件准备" tabindex="-1">硬件准备</h2>
<p>第一步还是一样的，弄清楚每个引脚的功能。不过需要注意的是，LCD12864 有很多变种，这些变种通常在引脚以及其功能上会有差异。笔者使用的这款驱动芯片是 ST7920。常见的芯片种类与差异如下表（由 Grok 汇总）<sup class="footnote-ref"><a href="#footnote1">[1]</a><a class="footnote-anchor" id="footnote-ref1"></a></sup><sup class="footnote-ref"><a href="#footnote2">[2]</a><a class="footnote-anchor" id="footnote-ref2"></a></sup><sup class="footnote-ref"><a href="#footnote3">[3]</a><a class="footnote-anchor" id="footnote-ref3"></a></sup><sup class="footnote-ref"><a href="#footnote4">[4]</a><a class="footnote-anchor" id="footnote-ref4"></a></sup>：</p>
<div class="table-container"><table>
<thead>
<tr>
<th><span>芯片型号</span><span>Chip Model</span></th>
<th>ST7920</th>
<th>ST7565</th>
<th>T6963C</th>
<th>KS0108</th>
</tr>
</thead>
<tbody>
<tr>
<td>制造商</td>
<td>Sitronix</td>
<td>Sitronix</td>
<td>Toshiba</td>
<td>Samsung</td>
</tr>
<tr>
<td>电压范围</td>
<td>2.7V ~ 5.5V (兼容 3.3V/5V)</td>
<td>1.8V ~ 3.3V (低压优先)</td>
<td>4.5V ~ 5.5V (需 5V)</td>
<td>4.5V ~ 5.5V (需 5V)</td>
</tr>
<tr>
<td>接口类型</td>
<td>并行 8-bit / 串行 (SPI-like，写-only)</td>
<td>并行 8-bit / 串行 (SPI，写-only)</td>
<td>并行 8-bit (需外部时钟)</td>
<td>并行 8-bit (多芯片页选择)</td>
</tr>
<tr>
<td>内置 RAM</td>
<td>是 (256×32 像素，约 1Kb，支持滚动)</td>
<td>是 (约 1Kb，支持读写)</td>
<td>否 (需外部 SRAM，灵活扩展)</td>
<td>是 (每个芯片 64×64 像素，总 1Kb，支持读写)</td>
</tr>
<tr>
<td>内存组织</td>
<td>水平字节 (8 像素/字节，类似您的图像模式：上半屏 0x80，下半屏 0x88)</td>
<td>垂直字节 (1 列 8 像素/字节，需转换数据)</td>
<td>水平字节 (灵活，8/16 像素/字节)</td>
<td>垂直字节 (分 2 页 64×64，需页切换)</td>
</tr>
<tr>
<td>速度</td>
<td>较慢 (串行模式 ~100kHz，适合简单图形)</td>
<td>较快 (SPI 模式支持高频，适合动态显示)</td>
<td>中等 (并行快，但外部 RAM 延迟)</td>
<td>慢 (页切换开销大，串行需缓冲)</td>
</tr>
<tr>
<td>特殊功能</td>
<td>兼容 HD44780 字符模式 (8×32 字符)，内置电荷泵 (无需外部 7660)；支持 CGRAM (自定义字符)</td>
<td>支持灰度 (部分变体)，RGB 背光兼容；低功耗，适合电池设备</td>
<td>支持更大分辨率 (至 240×128)；文本/图形混合，需外部字体 RAM</td>
<td>简单页模式 (2 芯片并联实现 128×64)；支持读操作 (易调试)</td>
</tr>
<tr>
<td>引脚数</td>
<td>约 16-20 (RS, E, RST, PSB 选接口)</td>
<td>约 11-16 (SCLK, SID, CS, A0)</td>
<td>约 24 (DB0-7, FS, CE 等，多引脚)</td>
<td>约 16 (DB0-7, CS1/CS2 页选, R/W)</td>
</tr>
<tr>
<td>优缺点</td>
<td>优点：兼容字符模式，易移植；缺点：串行写-only (需 MCU 缓冲</td>
<td>优点：低压串行，速度快；缺点：垂直内存需数据转置</td>
<td>优点：扩展性强；缺点：需外部 RAM，复杂</td>
<td>优点：支持读写，简单；缺点：页切换慢，不兼容串行</td>
</tr>
<tr>
<td>成本/可用性</td>
<td>低 (~5-10元)，常见于中国模块</td>
<td>低 (~5-10元)，Raspberry Pi 友好</td>
<td>中等 (~10-20元)，工业级</td>
<td>低 (~5元)，老款但易得</td>
</tr>
<tr>
<td>库/兼容</td>
<td>U8g2/ST7920</td>
<td>U8g2/ST7565，需调整垂直布局</td>
<td>U8g2/T6963C，需配置外部 RAM</td>
<td>U8g2/KS0108，支持但速度慢</td>
</tr>
</tbody>
</table>
</div><p>LCD12864 ST7920 的元件引脚及功能如下表：</p>
<div class="table-container"><table>
<thead>
<tr>
<th style="text-align:center"><span>引脚</span>Pins<span></span></th>
<th style="text-align:center"><span>符号</span><span>Symbol</span></th>
<th style="text-align:center"><span>电平</span><span>Level</span></th>
<th><span>描述</span><span>Description</span></th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">1</td>
<td style="text-align:center"><code>VDD</code></td>
<td style="text-align:center">-</td>
<td>接地</td>
</tr>
<tr>
<td style="text-align:center">2</td>
<td style="text-align:center"><code>VSS</code></td>
<td style="text-align:center">-</td>
<td>电源正极</td>
</tr>
<tr>
<td style="text-align:center">3</td>
<td style="text-align:center"><code>V0</code></td>
<td style="text-align:center">-</td>
<td>对比度调节</td>
</tr>
<tr>
<td style="text-align:center">4</td>
<td style="text-align:center"><code>RS</code></td>
<td style="text-align:center">L / H</td>
<td>寄存器选择，L：<strong>指令</strong>寄存器；H：<strong>数据</strong>寄存器</td>
</tr>
<tr>
<td style="text-align:center">5</td>
<td style="text-align:center"><code>R/W</code></td>
<td style="text-align:center">L / H</td>
<td>读写控制，L：<strong>写入</strong>；H：<strong>读取</strong></td>
</tr>
<tr>
<td style="text-align:center">6</td>
<td style="text-align:center"><code>E</code></td>
<td style="text-align:center">H</td>
<td>使能信号，高电平有效</td>
</tr>
<tr>
<td style="text-align:center">7</td>
<td style="text-align:center"><code>DB0</code></td>
<td style="text-align:center">L / H</td>
<td>数据位 0</td>
</tr>
<tr>
<td style="text-align:center">8</td>
<td style="text-align:center"><code>DB1</code></td>
<td style="text-align:center">L / H</td>
<td>数据位 1</td>
</tr>
<tr>
<td style="text-align:center">9</td>
<td style="text-align:center"><code>DB2</code></td>
<td style="text-align:center">L / H</td>
<td>数据位 2</td>
</tr>
<tr>
<td style="text-align:center">10</td>
<td style="text-align:center"><code>DB3</code></td>
<td style="text-align:center">L / H</td>
<td>数据位 3</td>
</tr>
<tr>
<td style="text-align:center">11</td>
<td style="text-align:center"><code>DB4</code></td>
<td style="text-align:center">L / H</td>
<td>数据位 4</td>
</tr>
<tr>
<td style="text-align:center">12</td>
<td style="text-align:center"><code>DB5</code></td>
<td style="text-align:center">L / H</td>
<td>数据位 5</td>
</tr>
<tr>
<td style="text-align:center">13</td>
<td style="text-align:center"><code>DB6</code></td>
<td style="text-align:center">L / H</td>
<td>数据位 6</td>
</tr>
<tr>
<td style="text-align:center">14</td>
<td style="text-align:center"><code>DB7</code></td>
<td style="text-align:center">L / H</td>
<td>数据位 7</td>
</tr>
<tr>
<td style="text-align:center">15</td>
<td style="text-align:center"><code>PSB</code></td>
<td style="text-align:center">L / H</td>
<td>接口选择，L：串行；H：8 / 4 位并行</td>
</tr>
<tr>
<td style="text-align:center">16</td>
<td style="text-align:center"><code>NC</code></td>
<td style="text-align:center">-</td>
<td>悬空</td>
</tr>
<tr>
<td style="text-align:center">17</td>
<td style="text-align:center"><code>RESET</code></td>
<td style="text-align:center">L / H</td>
<td>复位，低电平有效</td>
</tr>
<tr>
<td style="text-align:center">18</td>
<td style="text-align:center"><code>VOUT</code></td>
<td style="text-align:center">-</td>
<td>-10V 负电源输出</td>
</tr>
<tr>
<td style="text-align:center">19</td>
<td style="text-align:center"><code>LEDA</code></td>
<td style="text-align:center">-</td>
<td>背光正极</td>
</tr>
<tr>
<td style="text-align:center">20</td>
<td style="text-align:center"><code>LEDK</code></td>
<td style="text-align:center">-</td>
<td>背光负极</td>
</tr>
</tbody>
</table>
</div><p>可以看到，与 LCD1602 相比，两者的在引脚上的安排是极为相似的。需要额外注意的是，在本项目中将直接使用 8 位并行的方式传输数据，因此需要将<code>PSG</code>引脚设为高电平；而用于提供 -10V 电源输出的 <code>VOUT</code>，原本应该是通过一个<a href="https://en.wikipedia.org/wiki/Potentiometer" class="external-link" target="_blank">电位器</a>接电源，同时输出端接<code>V0</code>，通过调整电阻以调节屏幕的对比度；但是有的模块上可能已经附带了用于调整的电位器，因此就无需额外连接电路。</p>
<p>在本项目中，读取功能不会使用，因此<code>RW</code>直接拉低。<code>RS</code>，<code>E</code>和<code>RESET</code>分别接 Arduino 的 2，3，4 引脚。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-09-11-LCD12864-ST7920/img/LCD12864_Back.avif" alt="LCD 的背面。RV1 是用于调整对比度的电位器。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>LCD 的背面。RV1 是用于调整对比度的电位器。</span>
		</figcaption>
	
</figure><p></p>
<p>在大多数项目中，会使用<code>pinMode()</code>函数来控制引脚的数据方向，<code>pinOut()</code>函数来设置引脚的输出电平。除了这个方法以外，Arduino 的每个引脚都由三个 8 位寄存器控制，通常以 B，C，D 等字母命名，对应不同的端口：</p>
<ol>
<li><code>DDRx, Data Direction Register</code>控制引脚的数据方向。0 为<strong>输入</strong>，1 为<strong>输出</strong>。</li>
<li><code>PORTx, Port Output Register</code>控制着该端口引脚上的输出电平。0 为<strong>高电平</strong>，1 为<strong>低电平</strong>。</li>
<li><code>PINx, Port Input Register</code>读取输入引脚的状态。如果引脚是高电平，则3相应的位是 1；反之则为 0。<sup class="footnote-ref"><a href="#footnote5">[5]</a><a class="footnote-anchor" id="footnote-ref5"></a></sup></li>
</ol>
<p>虽然官方手册上并不推荐这种做法，但它的速度比通过<code>for</code>循环逐位写入快得多；而且这种底层操作对于深入理解硬件也大有裨益。不同的型号的芯片对于各个引脚的映射不一样，所以在使用时请务必先通过官方给出的 Pinout 确认各个引脚所属的端口。笔者使用的是 Arduino Mega 2560，采用的处理器是 ATmega2560。根据官方给出的引脚图，<span style="color: #f49d26;">橘黄色</span>背景的文字就是单片机的端口，而相同端口的引脚就可以直接使用寄存器来读写。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-09-11-LCD12864-ST7920/img/Arduino_Mega_2560_Pinout_1.avif" alt="Arduino Mega 2560 的引脚图。橘黄色背景的文字是该引脚的端口编号。Arduino" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>Arduino Mega 2560 的引脚图。橘黄色背景的文字是该引脚的端口编号。<a href="https://docs.arduino.cc/hardware/mega-2560/" class="external-link" target="_blank">Arduino</a></span>
		</figcaption>
	
</figure><p></p>
<p>若要直接使用<code>0-7</code>号引脚作为数据接口，由于<code>0</code>号和<code>1</code>号引脚有编程的串行通信和调试的用途，直接将它们作为 <a href="https://en.wikipedia.org/wiki/General-purpose_input/output" class="external-link" target="_blank">GPIO</a>可能会干扰下载或调试，因此不行；同时这些引脚也并不属于同一个端口，用寄存器来控制的话很麻烦。在这个项目中，笔者选择了<code>A0-A7</code>作为数据接口，因为它们可以直接使用<code>PORTF</code>和<code>DDRF</code>来控制；而且排成一排，可以直接插排线，看起来就整洁很多。虽然板子上这几个引脚旁标注了 ANALOG IN 的字样，这并不妨碍它们作为 GPIO 使用。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-09-11-LCD12864-ST7920/img/LCD12864_Fritzing.avif" alt="在 Fritzing 中绘制的电路。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>在 Fritzing 中绘制的电路。</span>
		</figcaption>
	
</figure><p></p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-09-11-LCD12864-ST7920/img/Connection.avif" alt="实物连接图。买了杜邦线和面包板用硬芯线是个正确的决定。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>实物连接图。买了杜邦线和面包板用硬芯线是个正确的决定。</span>
		</figcaption>
	
</figure><p></p>
<h2 id="程序编写" tabindex="-1">程序编写</h2>
<p>硬件的准备工作完成了以后，接下来就是软件层面的事情了。LCD12864 的初始化流程和 LCD1602 的非常相似。在等待电源电压稳定以后，两者都要经过功能设置 → 显示控制 → 清屏 → 输入模式这几步。指令格式如下：</p>
<div class="table-container"><table>
<thead>
<tr>
<th><span>指令</span><span>Instruction</span></th>
<th style="text-align:center">RS</th>
<th style="text-align:center">R/W</th>
<th style="text-align:center">D7</th>
<th style="text-align:center">D6</th>
<th style="text-align:center">D5</th>
<th style="text-align:center">D4</th>
<th style="text-align:center">D3</th>
<th style="text-align:center">D2</th>
<th style="text-align:center">D1</th>
<th style="text-align:center">D0</th>
</tr>
</thead>
<tbody>
<tr>
<td><span>功能设置</span><span>Function Set</span></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>1</code></td>
<td style="text-align:center"><code>DL</code></td>
<td style="text-align:center"><code>X</code></td>
<td style="text-align:center"><code>RE</code></td>
<td style="text-align:center"><code>G</code></td>
<td style="text-align:center"><code>X</code></td>
</tr>
<tr>
<td><span>显示控制</span><span>Display Control</span></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>1</code></td>
<td style="text-align:center"><code>D</code></td>
<td style="text-align:center"><code>C</code></td>
<td style="text-align:center"><code>B</code></td>
</tr>
<tr>
<td><span>清屏</span><span>Clear Screen</span></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>1</code></td>
</tr>
<tr>
<td><span>输入模式</span><span>Entry Mode Set</span></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>1</code></td>
<td style="text-align:center"><code>I/D</code></td>
<td style="text-align:center"><code>S</code></td>
</tr>
</tbody>
</table>
</div><p>注：<code>X</code>表示该位可以为任意值，不会造成影响。功能设置指令的<code>G</code>仅有在启用扩展指令集的情况下才能使用。</p>
<p>第一条是功能设置 (Function Set) 指令。<code>DL</code>是用来选择数据位的宽度的。低电平是 <strong>4 位</strong>，高电平是 <strong>8 位</strong>；而<code>RE</code>用来选择指令集。低电平是<strong>基本指令集</strong>，高电平是<strong>扩展指令集</strong>。本项目将使用 8 位并行模式通信，因此<code>DL = 1</code>。扩展指令集在后期实现显示图像等功能的时候会派上用场，不过现在的任务是先在屏幕上显示内置字库，因此使用基本指令集即可，<code>RE = 0</code>。</p>
<p>第二条是显示控制 (Display ON/OFF Control) 指令。<code>D</code>, <code>C</code>, <code>B</code>这三位分别对应着<strong>显示</strong>，<strong>光标</strong>以及<strong>字符闪烁</strong>。高电平为启用，低电平为关闭。</p>
<p>第三条是清屏 (Clear Screen) 指令。这个很简单，没有需要设置的参数。</p>
<p>最后一条是输入模式 (Entry Mode Set) 指令。<code>I/D</code>控制光标的移动方向，高电平为向右移动（递增），低电平为向左移动（递减）。<code>S</code>控制全屏移动。低电平时禁用，高电平时则根据<code>I/D</code>来进行全屏移动。例如，当<code>S</code>启用时，<code>I/D</code>为高电平则向右移动，反之则向左移动。</p>
<p>笔者采用的指令如下：</p>
<div class="table-container"><table>
<thead>
<tr>
<th><span>指令</span><span>Instruction</span></th>
<th style="text-align:center">RS</th>
<th style="text-align:center">R/W</th>
<th style="text-align:center">D7</th>
<th style="text-align:center">D6</th>
<th style="text-align:center">D5</th>
<th style="text-align:center">D4</th>
<th style="text-align:center">D3</th>
<th style="text-align:center">D2</th>
<th style="text-align:center">D1</th>
<th style="text-align:center">D0</th>
</tr>
</thead>
<tbody>
<tr>
<td><span>功能设置</span><span>Function Set</span></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>1</code></td>
<td style="text-align:center"><code>1</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
</tr>
<tr>
<td><span>显示控制</span><span>Display Control</span></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>1</code></td>
<td style="text-align:center"><code>1</code></td>
<td style="text-align:center"><code>1</code></td>
<td style="text-align:center"><code>0</code></td>
</tr>
<tr>
<td><span>清屏</span><span>Clear Screen</span></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>1</code></td>
</tr>
<tr>
<td><span>输入模式</span><span>Entry Mode Set</span></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>0</code></td>
<td style="text-align:center"><code>1</code></td>
<td style="text-align:center"><code>1</code></td>
<td style="text-align:center"><code>0</code></td>
</tr>
</tbody>
</table>
</div><p>OK，决定好要使用什么指令以后，接下来就是编程。上次驱动 LCD1602 时，是将这些指令的二进制数据直接保存在变量里。这样虽然调用时方便，但是若想使用不同的设置，还得手动更改变量的值，很不直观。因此这次将使用最近学会的一个好方法。</p>
<p>这个方法即自定义头文件。里面将<strong>声明 (Declaration)</strong> 一些函数。头文件取名为<code>ST7920.h</code>。初始化的流程一共要用到四条指令，现在就先声明四个函数用来专门控制：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C"><span class="hljs-comment">// ST7920.H</span>
<span class="hljs-meta">#<span class="hljs-keyword">ifndef</span> ST7920_H</span>
<span class="hljs-meta">#<span class="hljs-keyword">define</span> ST7920_H</span>

<span class="hljs-type">void</span> <span class="hljs-title function_">FuncSet</span><span class="hljs-params">()</span>;
<span class="hljs-type">void</span> <span class="hljs-title function_">DispCtrl</span><span class="hljs-params">()</span>;
<span class="hljs-type">void</span> <span class="hljs-title function_">ClrScreen</span><span class="hljs-params">()</span>;
<span class="hljs-type">void</span> <span class="hljs-title function_">EntryMode</span><span class="hljs-params">()</span>;

<span class="hljs-meta">#<span class="hljs-keyword">endif</span></span>
</code></pre>
	</div>
</div>

<p>诸位读者想必已经发现了：除了要声明的函数以外，还有三条语句：<code>#ifndef</code>，<code>#define</code>和<code>#endif</code>。它们是什么意思呢？</p>
<p>这三条是 C/C++ 的<a href="https://cppreference.cn/w/c/preprocessor" class="external-link" target="_blank">预处理器指令 (Preprocessor Directives)</a>。这类指令将用于控制编译器如何编译文件或编译哪些部分<sup class="footnote-ref"><a href="#footnote6">[6]</a><a class="footnote-anchor" id="footnote-ref6"></a></sup>。上述代码中出现的<code>#ifndef</code>，<code>#define</code>和<code>#endif</code>，是用于防止头文件被重复包含，通常成对出现。</p>
<p>那么为什么需要防止头文件被重复包含呢？简单来说，我们在一个源文件通过<code>#include</code>包含某个头文件时，就相当于直接将头文件里的内容复制粘贴在了代码里。假设有两个源文件<code>file1.c</code>和<code>file2.c</code>同时包含了<code>common.h</code>这个头文件，那么在编译时就相当于<code>common.h</code>中的内容被<strong>重复定义</strong>，这时编译器就会报错。<code>#ifndef</code>相当于一个<code>if ()</code>语句，会检测一个特定的<a href="https://en.wikipedia.org/wiki/Macro_(computer_science)" class="external-link" target="_blank">宏</a>，通常来说是头文件的文件名，例如<code>COMMON_H</code>，有没有被定义。如果没有定义，就执行接下来的<code>#define</code>定义这个宏，以及接下来的其他代码；而如果被定义了，那么接下来所有的代码都不会被执行，直到<code>#endif</code>结束。</p>
<p>除此之外，这三条指令也可以凝缩成为一条<code>#pragma once</code>。这个更简洁，同时也不用担心宏名冲突的问题。虽然是非标准的，不过目前大部分的编译器都支持这条命令，除非是那种特别特别老旧的版本。头文件也应尽量减少相互包含，避免形成循环依赖，正确的做法是在<code>.c</code>或<code>.cpp</code>的源文件中包含。</p>
<p>确定好要使用的函数以后，接下来就是要传入的参数。这些参数都可以视作布尔类型。但是如果在调用的时候直接写<code>DispCtrl(true, false, false)</code>的话也很不直观。因此可以通过<strong>枚举</strong><code>enum</code>来赋予这些参数一个有意义的名称。首先，根据每条指令要用到的参数，以这些参数为基准创建一个枚举类型，里面就可以列举出所有可能的情况。情况不外乎<code>0</code>和<code>1</code>两种，而这两种情况就可以分别取个易懂的名字：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C"><span class="hljs-comment">// Enums</span>
<span class="hljs-comment">// Entry Mode Set command</span>
<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">AddrContCtrl</span>   {</span> ADDR_INC = <span class="hljs-number">1</span>, ADDR_DEC  	= <span class="hljs-number">0</span> };
<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">DispShiftCtrl</span>  {</span> SHIFT_ON = <span class="hljs-number">1</span>, SHIFT_OFF 	= <span class="hljs-number">0</span> };

<span class="hljs-comment">// Function Set command</span>
<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">DataLength</span>     {</span> DL_8BIT  	= <span class="hljs-number">1</span>, DL_4BIT 	= <span class="hljs-number">0</span> };
<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">InstructionSet</span> {</span> EXTENDED 	= <span class="hljs-number">1</span>, BASIC    	= <span class="hljs-number">0</span> };
<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">GraphicMode</span>    {</span> GRAPHIC  	= <span class="hljs-number">1</span>, CHAR    	= <span class="hljs-number">0</span> };

<span class="hljs-comment">// Display Control command</span>
<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">DispSwitch</span>   	{</span> DISP_ON   = <span class="hljs-number">1</span>, DISP_OFF   = <span class="hljs-number">0</span> };
<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">CursorSwitch</span> 	{</span> CURSOR_ON = <span class="hljs-number">1</span>, CURSOR_OFF = <span class="hljs-number">0</span> };
<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">CharBlink</span>    	{</span> BLINK_ON  = <span class="hljs-number">1</span>, BLINK_OFF  = <span class="hljs-number">0</span> };

<span class="hljs-comment">// Write Byte command</span>
<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">RegSelect</span> 		{</span> DATA = <span class="hljs-number">1</span>, INST = <span class="hljs-number">0</span> };
</code></pre>
	</div>
</div>

<p>之前声明的函数就可以这样设定形参 (Formal Parameters)：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C"><span class="hljs-type">void</span> <span class="hljs-title function_">FuncSet</span><span class="hljs-params">(<span class="hljs-keyword">enum</span> DataLength DL, <span class="hljs-keyword">enum</span> InstructionSet RE, <span class="hljs-keyword">enum</span> GraphicMode G)</span>;
<span class="hljs-type">void</span> <span class="hljs-title function_">DispCtrl</span><span class="hljs-params">(<span class="hljs-keyword">enum</span> DispSwitch D, <span class="hljs-keyword">enum</span> CursorSwitch C, <span class="hljs-keyword">enum</span> CharBlink B)</span>;
<span class="hljs-type">void</span> <span class="hljs-title function_">ClrScreen</span><span class="hljs-params">()</span>;
<span class="hljs-type">void</span> <span class="hljs-title function_">EntryMode</span><span class="hljs-params">(<span class="hljs-keyword">enum</span> AddrContCtrl ID, <span class="hljs-keyword">enum</span> DispShiftCtrl S)</span>;
</code></pre>
	</div>
</div>

<p>现在，就可以这样来设置要发送的指令，例如：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C">FuncSet(DL_8BIT, BASIC, CHAR);
</code></pre>
	</div>
</div>

<p>意思就相当一目了然了，即 8 位数据模式，基础指令集，字符模式。</p>
<p>不过，这还只是声明好了函数。函数功能的实现，或者<strong>定义 (Definition)</strong> 应该放在<code>.c</code>或<code>.cpp</code>的源文件中实现，例如再新建一个<code>st7920.c</code>的文件。</p>
<p>由于<code>PORTF</code>一次性传递的是一个字节的数据，因此需要进行一些位操作来更改这一字节内某一位的值。这一过程可以通过<strong>按位或 (Bitwise OR)</strong> 以及 <strong>位移 (Bitwise Shift)</strong> 来完成。例如，在<code>FuncSet()</code>中，需要将 bit4 和 bit2 分别设为<code>1</code>和<code>0</code>。读取到传入的参数以后，分别将这两个向高位左移 4 位和 2 位，随后再通过按位或把值赋给这一位，即：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C"><span class="hljs-type">uint8_t</span> cmd = <span class="hljs-number">0b00100000</span> | (DL &lt;&lt; <span class="hljs-number">4</span>) | (RE &lt;&lt; <span class="hljs-number">2</span>);
</code></pre>
	</div>
</div>

<p>例如当<code>DL = 1, RE = 0</code>时：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">Maxima</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-Maxima">   <span class="hljs-number">0b00100000</span>
   <span class="hljs-number">0b00010000</span> (<span class="hljs-number">1</span> &lt;&lt; <span class="hljs-number">4</span> = <span class="hljs-number">0b00010000</span>)
OR <span class="hljs-number">0b00000000</span>
--------------
   <span class="hljs-number">0b00110000</span>
</code></pre>
	</div>
</div>

<p>最终的计算结果就是要传输的指令。</p>
<p>最后，除了这些和 LCD 功能有关的指令以外，还得有个负责将它们写入的函数。要写入的内容分为指令和数据两种，同时还得控制写完后的延迟的时间，以确保正确写入。根据这些需求，就可以声明：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C"><span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">RegSelect</span> {</span> DATA = <span class="hljs-number">1</span>, INST = <span class="hljs-number">0</span> };
<span class="hljs-type">void</span> <span class="hljs-title function_">WriteByte</span><span class="hljs-params">(<span class="hljs-keyword">enum</span> RegSelect rs, <span class="hljs-type">uint8_t</span> data, <span class="hljs-type">int</span> DelayUs)</span>
</code></pre>
	</div>
</div>

<p>第一个参数<code>rs</code>是选择要写入的寄存器类型，分为指令和数据两种寄存器。这个参数就直接控制着<code>RS</code>引脚上的电平。<code>data</code>即要写入的内容，通过<code>uint8_t</code>定义了一个大小为 8-bit 的形参。最后是要延迟的微秒数。根据手册可得，大部分的写指令都能在几百纳秒内完成，因此为了提升效率，一般延迟 10μs 左右就足够了，不过清屏的时间可以留得略久一点。除此之外，还得控制使能<code>E</code>引脚上的电平。这个引脚的功能可以理解为「发送数据」，高电平有效。这个函数的实现如下：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C"><span class="hljs-type">void</span> <span class="hljs-title function_">WriteByte</span><span class="hljs-params">(<span class="hljs-keyword">enum</span> RegSelect rs, <span class="hljs-type">uint8_t</span> data, <span class="hljs-type">int</span> delayUs)</span> {
	<span class="hljs-comment">// Set the mode of RS</span>
	<span class="hljs-comment">// true for data mode</span>
	<span class="hljs-comment">// false for instruction mode</span>
	<span class="hljs-keyword">if</span> (rs == <span class="hljs-literal">true</span>)
		digitalWrite(RS, HIGH);
	<span class="hljs-keyword">else</span>
		digitalWrite(RS, LOW);
	   
	<span class="hljs-comment">// Write the data the the data pins</span>
	PORTF = data;

	<span class="hljs-comment">// Generate a rising edge on pin Enable</span>
	digitalWrite(E, HIGH);
	delayMicroseconds(<span class="hljs-number">10</span>);
	digitalWrite(E, LOW);

	<span class="hljs-comment">// Delay for delayMs for component ready</span>
	delayMicroseconds(delayUs);
}
</code></pre>
	</div>
</div>

<h2 id="正式点亮" tabindex="-1">正式点亮</h2>
<p>终于，所有最基本的函数已经写好了，现在只需要在主文件中包含自定义的头文件即可调用这些函数：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C"><span class="hljs-comment">// LCD128+4.ino</span>

<span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"ST7920.h"</span></span>

<span class="hljs-type">void</span> <span class="hljs-title function_">setup</span><span class="hljs-params">()</span> {
	pinMode(RST, OUTPUT); <span class="hljs-comment">// Intialize RST pin (default HIGH)</span>
	pinMode(E, OUTPUT); <span class="hljs-comment">// Intialize E pin (default LOW)</span>
	pinMode(RS, OUTPUT); <span class="hljs-comment">// Intialize RS pin (default LOW)</span>

	DDRF = <span class="hljs-number">0xFF</span>; &nbsp;<span class="hljs-comment">// Set data direction output</span>
	PORTF = <span class="hljs-number">0x00</span>; <span class="hljs-comment">// Intialize pin output</span>

	DispClr();
	FuncSet(DL_8BIT, BASIC, CHAR);
	DispCtrl(DISP_ON, CURSOR_OFF, BLINK_OFF);
	DispClr();
	EntryMode(ADDR_INC, SHIFT_OFF);
}

</code></pre>
	</div>
</div>

<p>这样就完成了屏幕的初始化流程（其实这些函数也可以打包在一起，放在一个用于初始化的函数中）。而此时如果想在屏幕上输出字符，可以直接通过<code>WriteByte()</code>，选择数据模式，数据就填要打印的 ASCII 字符即可。如果想输出字符串，可以定义一个<code>char</code>类型的指针，并且在每次写完数据后向后移动一位。在这里我写了个函数专门用于输出字符串：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C"><span class="hljs-type">void</span> <span class="hljs-title function_">LcdPrint</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">char</span> *text)</span> {
&nbsp; &nbsp; <span class="hljs-keyword">while</span> (*text) {
&nbsp; &nbsp; &nbsp; &nbsp; WriteByte(DATA, *text, <span class="hljs-number">10</span>);
&nbsp; &nbsp; &nbsp; &nbsp; text++;
&nbsp; &nbsp; }
}

LcdPrint(<span class="hljs-string">"Hello World!"</span>);
</code></pre>
	</div>
</div>

<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-09-11-LCD12864-ST7920/img/Hello_World.avif" alt="在屏幕上输出 Hello World!" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>在屏幕上输出 Hello World!</span>
		</figcaption>
	
</figure><p></p>
<p>而如果此时直接输入中文字符串：</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-09-11-LCD12864-ST7920/img/Mojibake.avif" alt="输入的原字符串是「你好！」，结果屏幕上出现的是乱码。这种展开属于是意料之外，情理之中了。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>输入的原字符串是「你好！」，结果屏幕上出现的是乱码。这种展开属于是意料之外，情理之中了。</span>
		</figcaption>
	
</figure><p></p>
<p>出现<a href="https://en.wikipedia.org/wiki/Mojibake" class="external-link" target="_blank">乱码 (Mojibake)</a> 的原因很简单。西文部分由于是兼容 ASCII 码的，所以无论怎么样都没关系，能被正确解码；但是中文就不一样了。屏幕采用的编码是基于 <a href="https://en.wikipedia.org/wiki/GB_2312" class="external-link" target="_blank">GB2312</a> 的双字节编码；而代码默认采用的是 UTF-8。缺少字符间的映射关系的话就会导致乱码。这部分的原理比较复杂，而且偏离了本文的主题，在此先按下不表。代替方案就是先通过一些工具，例如 Python 有个encode()`函数，将返回一串字节串，这时再将编码输入函数就可以正确显示：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">Python</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-Python">(<span class="hljs-string">"你好！"</span>).encode(<span class="hljs-string">"GB2312"</span>) <span class="hljs-comment"># Return b'\xc4\xe3\xba\xc3\xa3\xa1'</span>
</code></pre>
	</div>
</div>

<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-09-11-LCD12864-ST7920/img/Hanzi.avif" alt="正确显示的字符串。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>正确显示的字符串。</span>
		</figcaption>
	
</figure><p></p>
<h2 id="后记" tabindex="-1">后记</h2>
<p>这篇文章篇幅虽然很长，但内容还是相当基础的，只用到了这块屏幕的最基础的功能。在后面的文章中，我将会侧重于它的图形模式，显示自定义字形，以及开发一些用于绘图的函数。</p>
<hr>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="footnote1" class="footnote-item"><p>ST7920 Datasheet(PDF) - Sitronix Technology Co., Ltd. <a href="https://www.alldatasheet.com/datasheet-pdf/pdf/326219/SITRONIX/ST7920.html" class="external-link" target="_blank">https://www.alldatasheet.com/datasheet-pdf/pdf/326219/SITRONIX/ST7920.html</a> <a href="#footnote-ref1" class="footnote-backref">↩︎</a></p>
</li>
<li id="footnote2" class="footnote-item"><p>ST7565 Datasheet(PDF) - Sitronix Technology Co., Ltd. <a href="https://www.alldatasheet.com/datasheet-pdf/pdf/326240/SITRONIX/ST7565.html" class="external-link" target="_blank">https://www.alldatasheet.com/datasheet-pdf/pdf/326240/SITRONIX/ST7565.html</a> <a href="#footnote-ref2" class="footnote-backref">↩︎</a></p>
</li>
<li id="footnote3" class="footnote-item"><p>T6963C Datasheet(PDF) - Toshiba Semiconductor. <a href="https://www.alldatasheet.com/datasheet-pdf/pdf/31129/TOSHIBA/T6963C.html" class="external-link" target="_blank">https://www.alldatasheet.com/datasheet-pdf/pdf/31129/TOSHIBA/T6963C.html</a> <a href="#footnote-ref3" class="footnote-backref">↩︎</a></p>
</li>
<li id="footnote4" class="footnote-item"><p>KS0108B Datasheet(PDF) - Samsung semiconductor. <a href="https://www.alldatasheet.com/datasheet-pdf/pdf/37323/SAMSUNG/KS0108B.html" class="external-link" target="_blank">https://www.alldatasheet.com/datasheet-pdf/pdf/37323/SAMSUNG/KS0108B.html</a> <a href="#footnote-ref4" class="footnote-backref">↩︎</a></p>
</li>
<li id="footnote5" class="footnote-item"><p>Arduino - PortManipulation | Arduino Documentation. <a href="https://docs.arduino.cc/retired/hacking/software/PortManipulation/" class="external-link" target="_blank">https://docs.arduino.cc/retired/hacking/software/PortManipulation/</a> <a href="#footnote-ref5" class="footnote-backref">↩︎</a></p>
</li>
<li id="footnote6" class="footnote-item"><p>C 预处理器 | 菜鸟教程. <a href="https://www.runoob.com/cprogramming/c-preprocessors.html" class="external-link" target="_blank">https://www.runoob.com/cprogramming/c-preprocessors.html</a> <a href="#footnote-ref6" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
</body></html>
						]]>
					</content:encoded>
				</item>
			

				<item>
					<title>温柔的幻境：从崩铁 × Fate 联动看「开拓之道」的精神内核</title>
					<link>https://studiountagged.top/articles/2025-07-22-A-Soft-Illusion</link>
					<guid isPermaLink="true">https://studiountagged.top/articles/2025-07-22-A-Soft-Illusion/</guid>
					<pubDate>Mon, 21 Jul 2025 16:00:00 GMT</pubDate>
					<author>Steven Liu</author>
					
					<description>
						<![CDATA[
							关于联动剧情启发带来的启示
The enlightment brought by the collab story.
							
						]]>
					</description>
					<content:encoded>
						<![CDATA[
							<html><head></head><body><p>《崩坏：星穹铁道》与《Fate UBW》联动这件事，对我来说其实没什么太大的波澜。我之前虽然也看过《Fate/stay night》的动画，也相当喜欢 Saber，有段时间甚至拿她当过壁纸，但那已经是很久很久以前的事了，很多设定已经记不太清了，有些在联动剧情中玩的梗也未必能够及时 get 到。抛开这些不谈，本次联动活动的剧情总体上还是以「欢愉」为主基调的，不过有一段令我印象深刻，故打算写一篇杂谈，来分享分享我个人的看法。</p>
<p>这段剧情概括一下，作为 Caster 的「音符小姐」为了拖住众人前进的脚步，将众人拖进了她所营造的幻境中。这个幻境所展现的正是众人心中的理想乡的模样，以此试图让众人忘却心中的使命，永远留在这个幻境之中。在这个幻境之中，我们的开拓者来到了另一条 if 线中——即如果当时没有踏上星穹列车，而是留在空间站会怎么样。在这条分支，开拓者变成了一个平平无奇的科员，每天都奔波于各种琐事，仿佛自己的一生能一眼从头看到尾。</p>
<p>对我而言，这段剧情是颇具警醒意味的。这不禁与《月亮与六便士》书中的那位画家形成了某种呼应。我很久以前读到这本书时，并不知道它为什么称得上是名著；然而在过完这段剧情以后，这层联想才使我在一定程度上理解了为什么主人公宁愿放弃自己的妻儿和稳定的工作，转而选择当一个漂泊不定、穷困潦倒的画家。开拓者在幻象中逐渐对日复一日的重复工作感到厌倦，而内心也在嘶声力竭地挣扎着，试图摆脱现状。我想，书中的画家在一定程度上和开拓者是一样的；又或许大家都一样。谁人年少时不曾拥有一腔热血，希望能够闯出一番天地、有所作为？然而总是会被现实毫不留情地当头一棒，被各种鸡毛蒜皮的小事束手缚脚，最后蓦然回首才发现自己已经垂垂老矣，只能碌碌无为地度过一生。</p>
<p>诚然，踏出第一步无疑需要巨大的勇气，无人知道前方究竟会有什么风险。这似乎是个两难的抉择；然而如果因此失去了前进的动力，最后也只会在遗忘中死去。对于整个世界来说，每个人刚诞生的时候都是如此渺小，平平无奇，与游戏中的 NPC 并无二异；然而总有些人能够脱颖而出，在人群中发出耀眼的光芒，甚至改写历史的进程。人类之所以能够向前发展，不是因为其聪明才智，或是周密计划；而是那些不甘平庸的灵魂，不肯放弃挣扎与思考。开拓者所踏上的开拓之道，不光光是对群星的开拓，也是对自身的开拓。我想，这就是策划想要表达的吧，虽然未来未知，但要永远不忘初心，永远胸怀热血，正如乔布斯的名言 <q>Stay hungry. Stay foolish.</q></p>
<p>音符小姐所构建的幻象并非空穴来风，现实中也存在着无痛的「理想乡」。如今我们处于一个信息爆炸的时代，快节奏的生活和娱乐的泛滥使得人们逐渐失去了批判思考与自我意识。尼尔·波兹曼对电视的诞生进行了批判，而他的这些话放在半个世纪后的今天仍然适用。赫胥黎的《美丽新世界》正在成为现实。而当所有人当沉湎于感官刺激和娱乐中，人类发展就已经停滞了，因为我们心甘情愿地住进了我们自己所营造的牢房之中；而历史上一切的伟大壮举和新思想都是源于苦难的。理想乡之所以令人动摇，并非是因为它是虚构的；反之，它一直就存在于现实生活中，而且触手可及。那是一种不痛不痒、不需要承担风险的生活，是所有烦恼与责任都被温水煮开的日常。相比之下，现实太难了，难得让人想逃。在这一点上，中国几千年前的一位大哲学家就已经提出了准确的观点：「生于忧患，死于安乐」。在这个娱乐至死的时代，是时候该慢下来，好好听听自己内心的声音了。「现在我真的拥有我想拥有的吗？」「还记得当初自己究竟是要干什么吗？」切忌被各种鸡毛蒜皮的小事遮蔽耳目。</p>
<p>哲学上有三大著名的发问：「我是谁」「我从哪里来」「我到哪里去」。在我看来，这些问题可以被分别解读为对自己的定位，自己的初心以及自己真正想要追求的东西。我因为高考完填错志愿被分配到了一个自己极其讨厌的专业，而且每天都还得学着自己讨厌的课程。别人口中所谓的「美好的大学时光」对我而言只不过是高中之后的另一个牢笼。对于专业课一次次的抵触和逃避带来的自然是堆成山的挂科和重修。而这次的剧情，使我意识到，一直逃避、一直沉浸在自己的世界中，恐怕将来的处境会变得和游戏中音符小姐所期望的那样。我开始认真思索这三个问题。也许我现在并不能给出答案，但这个过程将会让我能够重新听清忽视已久的内心的声音。</p>
<p>在《赛博朋克 2077》中德克斯特曾在车上问 V 的那个问题：「无名小卒？还是名扬天下？」，现在看来，我会坚定地选择后者。</p>
</body></html>
						]]>
					</content:encoded>
				</item>
			

				<item>
					<title>新增对 RSS 订阅的支持</title>
					<link>https://studiountagged.top/articles/2025-07-04-RSS-Available</link>
					<guid isPermaLink="true">https://studiountagged.top/articles/2025-07-04-RSS-Available/</guid>
					<pubDate>Thu, 03 Jul 2025 16:00:00 GMT</pubDate>
					<author>Steven Liu</author>
					
					<description>
						<![CDATA[
							可以通过各种主流 RSS 阅览器对本站文章进行订阅。
Subscribe to the articles on this site through various mainstream RSS readers.
							
						]]>
					</description>
					<content:encoded>
						<![CDATA[
							<html><head></head><body><p>目前本站已支持对 RSS 订阅的支持🎉。RSS (Really Simple Syndication) 是一种很棒的信息获取方式。通过 RSS，你可以在自己喜欢的阅读器中统一管理和阅读订阅的内容，不会错过任何更新，也不用担心算法推荐的干扰。在这里感谢 <a href="https://quarkpixel.github.io/" class="external-link" target="_blank">QuarkPixel</a> 给我提供的灵感。订阅方法如下：</p>
<ol>
<li>
<p>通过浏览器插件进行订阅。以 RSSHub Radar 为例，它可以自动检测到当前页面上的 RSS。</p>
</li>
<li>
<p>或者点击侧栏或页面最底部处橙色的 RSS 订阅 的链接；该链接将会直接导向<code>rss.xml</code>。你只需要将这个 XML 文件的链接复制粘贴进 RSS 阅览器中即可完成订阅。</p>
</li>
</ol>
<p>目前支持的订阅格式为 RSS 2.0。欢迎大家订阅！</p>
</body></html>
						]]>
					</content:encoded>
				</item>
			

				<item>
					<title>What’s the “fi” you see on the books</title>
					<link>https://studiountagged.top/articles/2025-05-26-The-Beauty-of-the-Typography-Ep01-EN</link>
					<guid isPermaLink="true">https://studiountagged.top/articles/2025-05-26-The-Beauty-of-the-Typography-Ep01-EN/</guid>
					<pubDate>Sun, 25 May 2025 16:00:00 GMT</pubDate>
					<author>Steven Liu</author>
					
					<description>
						<![CDATA[
							排印之美 Ep. 01
The Beauty of Typography Ep. 01
							
						]]>
					</description>
					<content:encoded>
						<![CDATA[
							<html><head></head><body><div class="youtube-video-container">
    <iframe width="560" height="315" src="https://www.youtube.com/embed/5RA6tvWzk7Q?si=qM37ryFvBdv1iDBZ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe>
</div>
<h2 id="video-transcripts" tabindex="-1">Video Transcripts</h2>
<div class="callout callout-sky" data-callout-type="note">
	<div class="callout-content">
		<div class="callout-title">
			<iconify-icon icon="material-symbols:info-outline-rounded"></iconify-icon>
			<span>
				注释
			</span>
		</div>
		<div class="callout-body"><p>简体中文版的视频文案请参阅<a href="/articles/2025-05-25-The-Beauty-of-the-Typography-Ep01/">此文章</a>。</p>
</div></div></div>
<p>Have you ever noticed, in some books, magazines or newspapers, and even on screens, you can always find these kinds of linked characters? So, what are these things? Are they some sort of printing or rendering errors How were these things created? And what's the purpose of their usage?</p>
<p>Greetings, I’m Steven. This series will introduce some knowledge of typeface designing and typography to you. Okay, let's begin!</p>
<p>These kinds of linked characters named Ligatures in typography. Ligature from Latin <em>ligātus</em> means “to tie, bind”. A ligature occurs where two or more letters are joined to form a single glyph.</p>
<p>Let’s date back to the ages when typesetting was the dominant printing method. Take a look at this metal type, I’ll flip it first for the demonstration. The letter sits in a rectangular space we call the bounding box. The width of this box includes the width of the glyph and side bearings. These side bearings help to keep the spacing between the letters looking just right in the ideal situation.</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-05-26-The-Beauty-of-the-Typography-Ep01-EN/img/Metrics.avif" alt="The bounding box, character width and side bearings of a metal type" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>The bounding box, character width and side bearings of a metal type</span>
		</figcaption>
	
</figure><p></p>
<p>However, certain letters such as f or T, A, V, can cause these huge visual gaps between the letters and reducing the readability because of their shape. Allowing a portion of the glyph to extend beyond the body can be a solution. And the overhanging part is called <em>kern</em>. But this can cause the collision between the adjacent letters, making them impossible to align neatly.</p>
<p>To solve this problem, the designers cast frequently used letter combinations, such as fi, fl, as individual metal type pieces. This practice gives rise to the ligatures. It not only enhanced visual harmony but also simplified the typesetting workflows. It quickly became popular and endures in modern digital design.</p>
<p>In contemporary typography, ligatures had evolved their original role of eliminating unsightly collisions and improving readability. They now serve as stylistic design elements. Powered by OpenType technology, designers can create not only standard ligatures but also various ligature functions—all meeting sophisticated typographic needs. Beyond simply connecting letters, modern ligatures do the opposite: enable subtle adjustments to adjacent glyphs, preventing visual clashing while maintaining cohesive word shapes.</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-05-26-The-Beauty-of-the-Typography-Ep01-EN/img/liga_and_dlig.avif" alt="Standard ligatures (liga) &amp; Discretionary ligatures (dlig) in EB Garamond" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>Standard ligatures (liga) &amp; Discretionary ligatures (dlig) in EB Garamond</span>
		</figcaption>
	
</figure>
<figure>
	<img src="https://studiountagged.top/articles/2025-05-26-The-Beauty-of-the-Typography-Ep01-EN/img/Cambria_ligature.avif" alt="Please pay attention to the highlighted parts. The fi ligature in Cambria separated instead connecting" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>Please pay attention to the highlighted parts. The fi ligature in Cambria separated instead connecting</span>
		</figcaption>
	
</figure><p></p>
<p>In many modern programming fonts, more and more type foundries are highlighting their support for various OpenType features as a selling point. One common approach is to alter the glyphs of frequently repeated symbols to improve the distinction between similar-looking content or to make repeated characters easier to count. Some fonts also enhance the readability of symbol combinations—for example, by rendering exclamation mark plus equal sign != as a widened, double-width not-equals sign <code>!=</code> for clearer recognition.</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-05-26-The-Beauty-of-the-Typography-Ep01-EN/img/Fira_Code_Ligatures.png" alt="Various ligatures in Fira Code" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>Various ligatures in Fira Code</span>
		</figcaption>
	
</figure><p></p>
<p>The ligatures mentioned above originate from typographic practices, where their function is purely aesthetic and technical in font design and typesetting, without altering the semantic meaning of the text. These are therefore termed&nbsp;<em>typographic ligatures</em>.</p>
<p>In contrast, another category exists, <em>orthographic ligatures</em>.&nbsp;These are not mere connected forms of two letters, nor can they be mechanically split back into their original components. Instead, they evolve into entirely new letters. The most iconic example is the letter&nbsp;W, whose name reflects its origin: called&nbsp;Double-U&nbsp;in English and&nbsp;Double-V&nbsp;in French. The origin of this letter won’t be discussed here today. So, if you are interested in this series, please leave a like and click subscribe. Comment below and let me know your ideas.</p>
<p>Another notable case is the German&nbsp;<em>eszett</em> (ß) or <em>Sharp S</em>. Although the name eszett, phonetically suggests a combination of&nbsp;Es&nbsp;and&nbsp;Zett, it is often written as a ligature of the&nbsp;long s (ſ)&nbsp;and&nbsp;round s (s). Tracing its roots, this character emerged from the Late Medieval and Early Modern German&nbsp;digraph ⟨Sz⟩, which in Gothic typography was stylized as a long s followed by a tailed z (ſʒ). So why is it written in combination of longs s plus round s today?</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-05-26-The-Beauty-of-the-Typography-Ep01-EN/img/Blackletters.avif" alt="Various glyph shapes of ß in Blackletter typefaces" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>Various glyph shapes of ß in Blackletter typefaces</span>
		</figcaption>
	
</figure><p></p>
<p>Between the 3rd and 8th centuries <span style="font-variant-caps: small-caps;">AD</span>, a significant phonetic shift occurred in Germanic languages known as the&nbsp;<em>High German Consonant Shift</em>. During this period, the West Germanic dialects that underwent the shift gradually evolved into Old High German, distinguishing themselves from non-shifted variants. The Proto-West Germanic voiceless stops /p/, /t/, and /k/ shifted to fricatives or affricates like /t͡s/, /p͡f/, and /x/.</p>
<p>However, due to the continued use of the Latin alphabet for written German, this led to inconsistencies such as multiple sounds represented by a single letter or multiple spellings for a single sound, with letter usage largely dictated by personal preference. In the original alphabet, the letter ⟨z⟩ represented both /t͡s/ and /s/. By the 13th century, ⟨s⟩ gradually became voiced as /z/, necessitating a distinction. Consequently, /s/ began to be denoted by ⟨ss⟩ or the digraph ⟨sz⟩. In Western typography, the ligature ſs (long s + round s) had long existed, but initially served purely as a typographic convention.</p>
<p>The pivotal moment came in July 1903, when the Leipzig Typographic Society officially adopted the <em>Sulzbacher</em> form as the foundational design for the Eszett (ß). This marked ß’s formal entry into the German alphabet. Finally, in 2017, the German Orthographic Council standardized the uppercase form, cementing ß’s definitive role in German orthography.</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-05-26-The-Beauty-of-the-Typography-Ep01-EN/img/Leipzig.avif" alt="„Zeitschrift für Deutschlands Buchbrucker Steindrucker und Verwandte Gewerbe“. Nr. 27, July 1903. In the announcement at the lower part of this page specified the shape of ß" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>„Zeitschrift für Deutschlands Buchbrucker Steindrucker und Verwandte Gewerbe“. Nr. 27, July 1903. In the announcement at the lower part of this page specified the shape of ß</span>
		</figcaption>
	
</figure><p></p>
<p>In contrast, the status of the digraph ⟨Ĳ⟩ in Dutch remains unresolved. Although the Dutch Language Union and many dictionaries recommend using ⟨i⟩ + ⟨j⟩ directly, it often behaves as an orthographic ligature in practice: when capitalizing initials, the entire ⟨ĳ⟩ is converted to uppercase; in Dutch crossword puzzles, they occupy a single square; on some typewriters, ⟨Ĳ⟩ has a dedicated key; The sign of Rijksmuseum designs it in this form; and many fonts design ⟨ĳ⟩ as a ligature. Unicode code points <code>U+0132</code> and <code>U+0133</code> explicitly name its uppercase and lowercase forms as <em>ligatures</em>. These factors undeniably reinforce the grapheme’s unified identity.</p>
<p>The evolution of ligatures is not limited to forming standalone letters. Over the course of history, frequently used letter combinations have gradually transformed into symbolic forms. A prime example is <em>ampersand</em> (&amp;), which signifies “and” and originates from the Latin et. Similarly, the number sign evolved from the Latin abbreviation <em>libra pondo</em> (℔). Additionally, currency symbols, alchemical symbols, and astrological signs also trace their origins to letter ligatures.</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-05-26-The-Beauty-of-the-Typography-Ep01-EN/img/Ampersand.avif" alt="The evolution of Ampersand (&amp;)" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>The evolution of Ampersand (&amp;)</span>
		</figcaption>
	
</figure>
<figure>
	<img src="https://studiountagged.top/articles/2025-05-26-The-Beauty-of-the-Typography-Ep01-EN/img/Libra_pondo_abbreviation_newton.avif" alt="The abbreviation of libra pondo wriiten by Issac Newton" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>The abbreviation of libra pondo wriiten by Issac Newton</span>
		</figcaption>
	
</figure><p></p>
<p>Ligatures are not exclusive to Latin scripts. Greek manuscript traditions included numerous ligatures, and influenced by this practice, early Greek printed works preserved such stylistic conventions. Today, Unicode still encodes ligatures like ⟨ϗ⟩<code>U+03D7</code>and ⟨ϛ⟩<code>U+03DB</code>. Cyrillic orthography also features many orthographic ligatures, most of which are included in Unicode. In Arabic, ligatures are indispensable—proper text rendering on screens requires extensive OpenType features. In East Asia, historically vertical Japanese writing employed ligatures or combined characters, though only a few are encoded today, such as hiragana <em>yori</em>「ゟ」<code>U+309F</code> and katakana <em>koto</em>「ヿ」<code>U+30FF</code>.</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-05-26-The-Beauty-of-the-Typography-Ep01-EN/img/Greek_Manuscripts.avif" alt="The manuscript of the Greek lectionary shows that it contains numerous ligatures." onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>The manuscript of the Greek lectionary shows that it contains numerous ligatures.</span>
		</figcaption>
	
</figure>
<figure>
	<img src="https://studiountagged.top/articles/2025-05-26-The-Beauty-of-the-Typography-Ep01-EN/img/Cyrillic_Ligatures.avif" alt="There's also a lot of orthographic ligatures in Cyrillic script" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>There's also a lot of orthographic ligatures in Cyrillic script</span>
		</figcaption>
	
</figure><p></p>
<p>Let us return to the opening question:&nbsp;<em>What purpose do these ligatures serve?</em> Beyond mere aesthetic pursuit, they are akin to DNA’s double helix—typographic and orthographic ligatures twist, soar, and waltz through time, etching their intricate dance into the annals of history. Through them, we glimpse the evolution of human language itself.</p>
<p>Though many typographic ligatures faded with the rise of phototypesetting and desktop publishing in the 20th century, OpenType rekindled their dance. Now, in the quiet spaces between letters, in the breath of every word, these humble symbols thread together the continuum of human expression—silent yet sovereign, they carry forward the unbroken story of who we are.</p>
<h2 id="references" tabindex="-1">References</h2>
<h3 id="books-manuscripts" tabindex="-1">Books &amp; Manuscripts</h3>
<ul>
<li>Aristotle. <a href="https://ub.unibas.ch/cmsdata/spezialkataloge/gg/higg0127.html$0" class="external-link" target="_blank"><em>Nicomachaen Ethics</em></a>. Basel: Johannes Oporin und Eusebius Episcopius, 1566.</li>
<li><a href="https://cudl.lib.cam.ac.uk/view/MS-ADD-00679/1$0" class="external-link" target="_blank">Greek Lectionary</a>. Cambridge University Library, MS Add. 679.</li>
<li>Arrighi, Ludovico degli. <a href="https://archive.org/details/laoperinadiludou00arri/page/n5/mode/2up$0" class="external-link" target="_blank"><em>La operina di Ludouico Vicentino, da imparare di scriuere littera cancellarescha</em></a>, 1524.</li>
<li><em>Zeitschrift für Deutschlands Buchdrucker Steindrucker und Verwandte Gewerbe</em>, 1903.</li>
</ul>
<h3 id="articles-columns" tabindex="-1">Articles &amp; Columns</h3>
<ul>
<li>Ralf Hermann. <a href="https://typography.guru/journal/whats-a-ligature/$0" class="external-link" target="_blank">“Typographic Myth Busting: What’s a Ligature, Anyway?”</a>. <em>Typography.guru</em>, Nov 20, 2012.</li>
<li>AMB. <a href="https://blog.as.uky.edu/thebhlog/?p=91$0" class="external-link" target="_blank">“The High German Sound Shift”</a>. <em>The Bʰlog</em>, Apr 28, 2014.</li>
<li>Zui. <a href="https://thelanguagecloset.com/2022/11/05/the-story-of-eszett-s/" class="external-link" target="_blank">“The Story of Eszett (ß)”</a>. <em>The Language Closet</em>, Nov 5, 2022.</li>
<li>Siphercase. <a href="https://vistudium.top/2023/02/16/e04_ligatures/$0" class="external-link" target="_blank">“万『字』皆可『连』 (影片文案)”</a>. <em>遠見齋</em>, 2023.</li>
</ul>
<h3 id="wikipedia-wiktionary-entries" tabindex="-1">Wikipedia &amp; Wiktionary Entries</h3>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Ligature_(writing)$0" class="external-link" target="_blank">“Ligature (writing)”</a>. <em>Wikipedia</em>, 2025.</li>
<li><a href="https://en.wikipedia.org/wiki/%C3%9F$0" class="external-link" target="_blank">“ß”</a>. <em>Wikipedia</em>, 2025.</li>
<li><a href="https://en.wikipedia.org/wiki/High_German_consonant_shift" class="external-link" target="_blank">“High German consonant shift”</a>. <em>Wikipedia</em>, 2025.</li>
<li><a href="https://en.wikipedia.org/wiki/IJ_(digraph)$0" class="external-link" target="_blank">“IJ (digraph)”</a>. <em>Wikipedia</em>, 2025.</li>
<li><a href="https://en.wikipedia.org/wiki/Ampersand" class="external-link" target="_blank">“Ampersand”</a>. <em>Wikipedia</em>, 2025.</li>
<li><a href="https://en.wikipedia.org/wiki/Number_sign" class="external-link" target="_blank">“Number sign”</a>. <em>Wikipedia</em>, 2025.</li>
<li><a href="https://en.wikipedia.org/wiki/Currency_symbol" class="external-link" target="_blank">“Currency symbol”</a>. <em>Wikipedia</em>, 2025.</li>
<li><a href="https://en.wikipedia.org/wiki/Alchemical_symbol" class="external-link" target="_blank">“Alchemical symbol”</a>. <em>Wikipedia</em>, 2025.</li>
<li><a href="https://en.wiktionary.org/wiki/ligature" class="external-link" target="_blank">“ligature”</a>. <em>Wiktionary</em>, 2025.</li>
<li><a href="https://ja.wikipedia.org/wiki/%E5%90%88%E7%95%A5%E4%BB%AE%E5%90%8D$0" class="external-link" target="_blank">“合略仮名”</a>. <em>Wikipedia</em>, 2025.</li>
</ul>
<h3 id="documentations-forums" tabindex="-1">Documentations &amp; Forums</h3>
<ul>
<li>FontForge Project. <a href="https://fontforge.org/docs/tutorial/editexample4.html#creating-a-ligature$0" class="external-link" target="_blank">“6.2. Creating a Ligature”</a>. <em>FontForge Documentation</em>.</li>
<li>Topp-Thema: <a href="https://www.typografie.info/3/topic/16219-topp-thema-eszett-als-gro%C3%9Fbuchstabe/$0" class="external-link" target="_blank">“Eszett als Großbuchstabe”</a>. <em>Typografie.info</em>, Mar 8, 2003.</li>
<li>Fira Code. <a href="https://github.com/tonsky/FiraCode$0" class="external-link" target="_blank">“Fira Code: free monospaced font with programming ligatures”</a>.</li>
<li><a href="https://web.archive.org/web/20170706162042/http://www.rechtschreibrat.com/DOX/rfdr_Regeln_2017.pdf$0" class="external-link" target="_blank">“Regeln und Wörterverzeichnis”</a>. <em>Deutsche Rechtschreibung</em>, 2017.</li>
</ul>
</body></html>
						]]>
					</content:encoded>
				</item>
			

				<item>
					<title>书本上的 fi 究竟是什么？</title>
					<link>https://studiountagged.top/articles/2025-05-25-The-Beauty-of-the-Typography-Ep01</link>
					<guid isPermaLink="true">https://studiountagged.top/articles/2025-05-25-The-Beauty-of-the-Typography-Ep01/</guid>
					<pubDate>Sat, 24 May 2025 16:00:00 GMT</pubDate>
					<author>Steven Liu</author>
					
					<description>
						<![CDATA[
							排印之美 Ep. 01
The Beauty of Typography Ep. 01
							
						]]>
					</description>
					<content:encoded>
						<![CDATA[
							<html><head></head><body><div class="youtube-video-container">
    <iframe width="560" height="315" src="https://www.youtube.com/embed/5RA6tvWzk7Q?si=qM37ryFvBdv1iDBZ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe>
</div>
<h2 id="视频文案" tabindex="-1">视频文案</h2>
<div class="callout callout-sky" data-callout-type="note">
	<div class="callout-content">
		<div class="callout-title">
			<iconify-icon icon="material-symbols:info-outline-rounded"></iconify-icon>
			<span>
				Note
			</span>
		</div>
		<div class="callout-body"><p>The English version video transcript is available at <a href="/articles/2025-05-26-The-Beauty-of-the-Typography-Ep01-EN/">here</a>.</p>
</div></div></div>
<p>不知道你是否注意过，在一些书籍、杂志、报纸，乃至屏幕上，总能找到这样连起来的字符？所以，这究竟是什么？是某种印刷或者显示错误吗？它们又是怎么被创造出来的？用途又是什么？大家好，我是 Steven。本系列将会向你介绍一些字体设计以及排印的知识。OK，步入正题。</p>
<p>这类连起来的字符其实在排印里面称作「连字」，并非印刷或显示错误。Ligature 源于拉丁语 <em>ligātus</em>，意为「连接，结合」。当两个或多个字母组合在一起形成一个单一的字形时，就会出现连字。</p>
<p>回溯至铅字还是主要印刷方式的时代。观察这个铅字，为了方便演示，我先将它镜像翻转一下。字母坐在我们称之为「字身框」的矩形方框中。方框的宽度包含了字形的宽度以及两侧的侧边距。在理想情况下，这些侧边距有助于保持字母之间的间距，使它们看起来恰到好处。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-05-25-The-Beauty-of-the-Typography-Ep01/img/Metrics.avif" alt="铅字的字身框、字符宽度和侧边距" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>铅字的字身框、字符宽度和侧边距</span>
		</figcaption>
	
</figure><p></p>
<p>但有时候，某些字母例如 f，或者 T, A, V，因为字形的原因，会导致视觉上这些巨大空白的出现，降低可读性。把字面的一部分伸出字身框是个解决办法。这些伸出去的部分就叫 kern。但这也会导致伸出的部分会和其他字母发生碰撞，根本无法和其他字母并列放置。同时也不利于收纳，铅字使用的时间一长也会导致字面发生更大程度的磨损。</p>
<p>为了解决这个问题，设计师们将常用的字母组合设计成单独的活字。连字也就应运而生。由于这种设计不仅使得版面整齐美观，而且也使拣字和排版更加便利，因此在那个时代很快就蔚然成风，直到今天。</p>
<p>在如今的语义下，连字的功能可以分为两大类：除了消除字符相连时可能会出现的难看的黑块，优化阅读体验的标准连字以外，还有一种就是更具有装饰性的设计，即自由连字。自由连字的使用并非必须，但由于它们大多都起源于活字印刷乃至手抄本的时代，因此酌情使用更能为设计作品添加一份古典的气息。在 OpenType 技术的加持下，除了标准连字以外，设计师还可以设计各式各样的连字功能，以满足更复杂的排版需求。连字除了将字「连」起来，也可以反其道而行之，将前后字符微调特意隔开，避免发生粘连。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-05-25-The-Beauty-of-the-Typography-Ep01/img/liga_and_dlig.avif" alt="EB Garamond 中的标准连字和自由连字" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>EB Garamond 中的标准连字和自由连字</span>
		</figcaption>
	
</figure>
<figure>
	<img src="https://studiountagged.top/articles/2025-05-25-The-Beauty-of-the-Typography-Ep01/img/Cambria_ligature.avif" alt="请仔细观察图中高亮的部分。在 Cambria 中，fi 连字反其道而行之，并没有将字「连」起来，而是隔开" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>请仔细观察图中高亮的部分。在 Cambria 中，fi 连字反其道而行之，并没有将字「连」起来，而是隔开</span>
		</figcaption>
	
</figure><p></p>
<p>在现代的一些编程字体中，也能看到越来越多的厂商开始以支持各种 OpenType 特性作为宣传点。最常见的就是对一些重复出现的符号改变字形，以便于区分相近的内容，或者使重复的符号更容易计数。也可以使一些符号组合更容易辨认，例如将 != 渲染成两倍宽的不等号 <code>!=</code>。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-05-25-The-Beauty-of-the-Typography-Ep01/img/Fira_Code_Ligatures.png" alt="Fira Code 中设计的各种连字" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>Fira Code 中设计的各种连字</span>
		</figcaption>
	
</figure><p></p>
<p>上面提到的连字是出现于排版印刷中的，它们功能仅在字体设计和排印上才能体现，而不会对原文的意义产生影响，因此也被称为<strong>排印学连字</strong>。而还有另外一种连字，这种连字不单单是将两个字母连写起来，也不是简单地将其拆开成原始字母就能作为替换，而是由此诞生了一个新的字母，称为<strong>正字法连字</strong>。</p>
<p>最有名的一个例子便是 <strong>W</strong>。根据它的读法就可见一斑，英语里叫 Double-U，法语中叫 Double-V。关于这个字母的起源，今天在这视频中就暂时不做过多阐述。如果你对本系列感兴趣的话，请点点赞，关注一下，在评论区留言让我知道你的想法。</p>
<p>此外还有德语中的 <strong>eszett</strong>。虽然依据读音是 Es+Zett 的形式，但却经常写作长 s (ſ) + 圆 s (s) 的正字法连字。寻其本源，这个字母最开始起源于中世纪晚期和现代早期德语的二合字母 ⟨Sz⟩，在 Blackletter 中就写成了 ſ + 带尾 z(ʒ) 的形式。既然如此，为什么它现在又会写成 ſ + s 的形式呢？</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-05-25-The-Beauty-of-the-Typography-Ep01/img/Blackletters.avif" alt="Eszett 在各种 Blackletter 字体中的字形" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>Eszett 在各种 Blackletter 字体中的字形</span>
		</figcaption>
	
</figure><p></p>
<p>在公元 3 至 8 世纪的时候，德语的语音发生了比较重要的变化，即高地德语辅音推移。在这期间，发生了推移的西日耳曼语逐渐形成了旧高地德语，区别于其他未发生推移的变体。原本西日耳曼语中的清塞音 /p/, /t/ 和 /k/ 演变成了擦音或塞擦音 /t͡s/, /p͡f/ 和 /x/。然而，由于当时的德语主要使用的还是拉丁字母，因此就出现了一字多音或一音多字的混乱局面。怎么使用这些字母全凭使用者的喜好。</p>
<p>在原来的字母表中，字母 ⟨z⟩ 既表 /t͡s/，又表 /s/。而到了 13 世纪，字母 ⟨s⟩ 逐渐浊化，开始发 /z/ 的音。为了区分，/s/ 就开始由 ⟨ss⟩ 或二合字母 ⟨sz⟩ 表记。在西文排印中，ſs 这个连字其实在就存在了，只是当时仅仅只是作为一个排印学连字存在。</p>
<p>而后来在 1903 年 7 月莱比锡字体排印学会发布的公告上，选用了一种名为 Sulz­bacher 式的字形作为 Eszett 的字形基础。自此，Eszett 就正式作为一个字母进入了德语。2017 年，德语正字法协会正式将 Eszett 的大写纳入了德语正字法。Eszett 在德语中的用法从此就尘埃落定。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-05-25-The-Beauty-of-the-Typography-Ep01/img/Leipzig.avif" alt="1903 年 7 月第 27 期的《面向德国图书印刷商、平版印刷商及相关行业的杂志（机翻）》。在这一页下半部分的公告中规范了 ß 的字形" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>1903 年 7 月第 27 期的《面向德国图书印刷商、平版印刷商及相关行业的杂志（机翻）》。在这一页下半部分的公告中规范了 ß 的字形</span>
		</figcaption>
	
</figure><p></p>
<p>而与之相对的，荷兰语中的二合字母 ⟨Ĳ⟩ 至今依然悬而未决。尽管荷兰语联盟和不少字典，都推荐这个字母现在应该直接使用 I + J 的形式，然而不少情况下它的表现都像是一个正字法连字。在首字母大写时，整个 ⟨ĳ⟩ 都会变成大写的形式；在荷兰语的纵横字谜中，它们也会被填入同一个格子；在一些打字机上，这个字母也分配到了单独的键位；阿姆斯特丹国家博物馆的标志将其设计成这种形式；在不少字体中，⟨ĳ⟩ 也被设计成了连字的形式。Unicode 码位<code>U+0132</code>和<code>U+0133</code>也将其大写和小写的形式称为 ligature。这些无疑又在强调这个字母的整体性。</p>
<p>形成单独的字母并非连字唯一的演化方向。一些常用的字母组合也在历史的长河中慢慢进化成了符号的样式。与号（&amp;）便是绝佳的证明。这个符号表示「与，和」的意思，而它就来源于拉丁语中的 <em>et</em>；而井号（#）最开始也是来自拉丁语 <em>libra pondo</em> 的缩写。除此之外，不少国家的货币符号，炼金术符号，天体符号等，都是字母连字的产物。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-05-25-The-Beauty-of-the-Typography-Ep01/img/Ampersand.avif" alt="与号（&amp;）的演化" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>与号（&amp;）的演化</span>
		</figcaption>
	
</figure>
<figure>
	<img src="https://studiountagged.top/articles/2025-05-25-The-Beauty-of-the-Typography-Ep01/img/Libra_pondo_abbreviation_newton.avif" alt="由艾萨克·牛顿书写的 libra pondo 的缩写" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>由艾萨克·牛顿书写的 libra pondo 的缩写</span>
		</figcaption>
	
</figure><p></p>
<p>连字也并非仅局限于拉丁字：希腊字母的手抄本中就包含了大量的连字，受其影响，早期的希腊语出版物中也保留了这种风格。如今，你还可以在 Unicode 中找到 ⟨ϗ⟩ 和 ⟨ϛ⟩ 的码位；在西里尔字母中，也存在着不少正字法连字，而且大多都被收录进了 Unicode；而在阿拉伯语中更是不可或缺，在屏幕上正确渲染文本需要涉及到大量的 OpenType 特性。在亚洲地区，曾经直排的日文也拥有不少连字或合字，如今被收录的只有平假名「ゟ」<code>U+309F</code>和片假名「ヿ」<code>U+30FF</code>，等等。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-05-25-The-Beauty-of-the-Typography-Ep01/img/Greek_Manuscripts.avif" alt="希腊语字典的手抄本，可以看到其中包含了大量的连字" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>希腊语字典的手抄本，可以看到其中包含了大量的连字</span>
		</figcaption>
	
</figure>
<figure>
	<img src="https://studiountagged.top/articles/2025-05-25-The-Beauty-of-the-Typography-Ep01/img/Cyrillic_Ligatures.avif" alt="西里尔字母中也有大量的正字法连字" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>西里尔字母中也有大量的正字法连字</span>
		</figcaption>
	
</figure><p></p>
<p>让我们回到最开始的问题。「这些连字有什么用处」。我想，这不仅仅是人们在美学上的尝试，除此之外，如同 DNA 的双螺旋结构，排印学和正字法的连字互相缠绕、纷飞、共舞，在历史的长河中留下了浓墨重彩的一笔，而我们也能以此得以窥见人类语言发展的历史。</p>
<p>不少排印学连字在 20 世纪兴起的照排以及桌面出版中逐渐没落，而 OpenType 的出现则再次让它们重焕生机。这些小小的符号，在字里行间、一呼一吸之间，承上启下人类的文明。</p>
<h2 id="参考链接" tabindex="-1">参考链接</h2>
<h3 id="书籍及手稿" tabindex="-1">书籍及手稿</h3>
<ul>
<li>Aristotle. <a href="https://ub.unibas.ch/cmsdata/spezialkataloge/gg/higg0127.html$0" class="external-link" target="_blank"><em>Nicomachaen Ethics</em></a>. Basel: Johannes Oporin und Eusebius Episcopius, 1566.</li>
<li><a href="https://cudl.lib.cam.ac.uk/view/MS-ADD-00679/1$0" class="external-link" target="_blank">Greek Lectionary</a>. Cambridge University Library, MS Add. 679.</li>
<li>Arrighi, Ludovico degli. <a href="https://archive.org/details/laoperinadiludou00arri/page/n5/mode/2up$0" class="external-link" target="_blank"><em>La operina di Ludouico Vicentino, da imparare di scriuere littera cancellarescha</em></a>, 1524.</li>
<li><em>Zeitschrift für Deutschlands Buchdrucker Steindrucker und Verwandte Gewerbe</em>, 1903.</li>
</ul>
<h3 id="文章及专栏" tabindex="-1">文章及专栏</h3>
<ul>
<li>Ralf Hermann. <a href="https://typography.guru/journal/whats-a-ligature/$0" class="external-link" target="_blank">"Typographic Myth Busting: What’s a Ligature, Anyway?"</a>. <em>Typography.guru</em>, Nov 20, 2012.</li>
<li>AMB. <a href="https://blog.as.uky.edu/thebhlog/?p=91$0" class="external-link" target="_blank">"The High German Sound Shift"</a>. <em>The Bʰlog</em>, Apr 28, 2014.</li>
<li>Zui. <a href="https://thelanguagecloset.com/2022/11/05/the-story-of-eszett-s/" class="external-link" target="_blank">"The Story of Eszett (ß)"</a>. <em>The Language Closet</em>, Nov 5, 2022.</li>
<li>Siphercase. <a href="https://vistudium.top/2023/02/16/e04_ligatures/$0" class="external-link" target="_blank">"万『字』皆可『连』 (影片文案)"</a>. <em>遠見齋</em>, 2023.</li>
</ul>
<h3 id="维基百科及维基词典" tabindex="-1">维基百科及维基词典</h3>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Ligature_(writing)$0" class="external-link" target="_blank">"Ligature (writing)"</a>. <em>Wikipedia</em>, 2025.</li>
<li><a href="https://en.wikipedia.org/wiki/%C3%9F$0" class="external-link" target="_blank">"ß"</a>. <em>Wikipedia</em>, 2025.</li>
<li><a href="https://en.wikipedia.org/wiki/High_German_consonant_shift" class="external-link" target="_blank">"High German consonant shift"</a>. <em>Wikipedia</em>, 2025.</li>
<li><a href="https://en.wikipedia.org/wiki/IJ_(digraph)$0" class="external-link" target="_blank">"IJ (digraph)"</a>. <em>Wikipedia</em>, 2025.</li>
<li><a href="https://en.wikipedia.org/wiki/Ampersand" class="external-link" target="_blank">"Ampersand"</a>. <em>Wikipedia</em>, 2025.</li>
<li><a href="https://en.wikipedia.org/wiki/Number_sign" class="external-link" target="_blank">"Number sign"</a>. <em>Wikipedia</em>, 2025.</li>
<li><a href="https://en.wikipedia.org/wiki/Currency_symbol" class="external-link" target="_blank">"Currency symbol"</a>. <em>Wikipedia</em>, 2025.</li>
<li><a href="https://en.wikipedia.org/wiki/Alchemical_symbol" class="external-link" target="_blank">"Alchemical symbol"</a>. <em>Wikipedia</em>, 2025.</li>
<li><a href="https://en.wiktionary.org/wiki/ligature" class="external-link" target="_blank">"ligature"</a>. <em>Wiktionary</em>, 2025.</li>
<li><a href="https://ja.wikipedia.org/wiki/%E5%90%88%E7%95%A5%E4%BB%AE%E5%90%8D$0" class="external-link" target="_blank">"合略仮名"</a>. <em>Wikipedia</em>, 2025.</li>
</ul>
<h3 id="文档及论坛" tabindex="-1">文档及论坛</h3>
<ul>
<li>FontForge Project. <a href="https://fontforge.org/docs/tutorial/editexample4.html#creating-a-ligature$0" class="external-link" target="_blank">"6.2. Creating a Ligature"</a>. <em>FontForge Documentation</em>.</li>
<li>Topp-Thema: <a href="https://www.typografie.info/3/topic/16219-topp-thema-eszett-als-gro%C3%9Fbuchstabe/$0" class="external-link" target="_blank">"Eszett als Großbuchstabe"</a>. <em>Typografie.info</em>, Mar 8, 2003.</li>
<li>Fira Code. <a href="https://github.com/tonsky/FiraCode$0" class="external-link" target="_blank">"Fira Code: free monospaced font with programming ligatures"</a>.</li>
<li><a href="https://web.archive.org/web/20170706162042/http://www.rechtschreibrat.com/DOX/rfdr_Regeln_2017.pdf$0" class="external-link" target="_blank">"Regeln und Wörterverzeichnis"</a>. <em>Deutsche Rechtschreibung</em>, 2017.</li>
</ul>
</body></html>
						]]>
					</content:encoded>
				</item>
			

				<item>
					<title>通过 Arduino 驱动 LCD1602（一）</title>
					<link>https://studiountagged.top/articles/2025-04-30-LCD1602</link>
					<guid isPermaLink="true">https://studiountagged.top/articles/2025-04-30-LCD1602/</guid>
					<pubDate>Tue, 29 Apr 2025 16:00:00 GMT</pubDate>
					<author>Steven Liu</author>
					
					<description>
						<![CDATA[
							从底层控制 LCD1602 的显示
Control the display of LCD1602 from the bottom layer
							
						]]>
					</description>
					<content:encoded>
						<![CDATA[
							<html><head></head><body><h2 id="前言" tabindex="-1">前言</h2>
<p>为什么突然想使用 Arduino 来控制 LCD 1602？原因很简单，因为现在正在学习数字电路，下个学期又要接触单片机。所以一半是为了预习，另一半也只是纯粹为了好玩，而且手头上正好也有这么一个元件，就开始了折腾。</p>
<p>我最早是受到了 <a href="https://www.youtube.com/watch?v=hZRL8luuPb8" class="external-link" target="_blank">The 8-Bit Guy</a> 和 <a href="https://www.youtube.com/watch?v=FY3zTUaykVo&amp;t=414s" class="external-link" target="_blank">Ben Eater</a> 的视频的启发。观看这两位的视频后，觉得 LCD 1602 的引脚功能很好理解，于是便买了一个回来。得益于批量生产，这种元件的价格已经相当廉价且皮实耐造， 20 块左右便到手了。</p>
<h2 id="元件功能" tabindex="-1">元件功能</h2>
<p>首要的任务便是弄清楚每个引脚的具体功能，然后再连接到 Arduino 上面。引脚的功能如下表：</p>
<table>
    <thead>
        <tr>
            <th><span>引脚</span><span>Pins</span></th>
            <th><span>符号</span><span>Symbol</span></th>
            <th><span>电平</span><span>Level</span></th>
            <th style="text-align: left"><span>描述</span><span>Description</span></th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>1</td>
            <td><code>GND</code></td>
            <td>0V</td>
            <td>接地</td>
        </tr>
        <tr>
            <td>2</td>
            <td><code>VCC</code></td>
            <td>5.0V</td>
            <td>正极</td>
        </tr>
        <tr>
            <td>3</td>
            <td><code>VO</code></td>
            <td>（可变）</td>
            <td>对比度调节</td>
        </tr>
        <tr>
            <td>4</td>
            <td><code>RS</code></td>
            <td>0/1</td>
            <td>寄存器选择，低电平为<strong>指令</strong>寄存器，高电平为<strong>数据</strong>寄存器</td>
        </tr>
        <tr>
            <td>5</td>
            <td><code>R/W</code></td>
            <td>0/1</td>
            <td>读写控制，低电平为<strong>写入</strong>，高电平为<strong>读取</strong></td>
        </tr>
        <tr>
            <td>6</td>
            <td><code>E</code></td>
            <td>1, 1 → 0</td>
            <td>使能，脉冲信号触发</td>
        </tr>
        <tr>
            <td>7</td>
            <td><code>DB0</code></td>
            <td>0/1</td>
            <td>数据位 0</td>
        </tr>
        <tr>
            <td>8</td>
            <td><code>DB1</code></td>
            <td>0/1</td>
            <td>数据位 1</td>
        </tr>
        <tr>
            <td>9</td>
            <td><code>DB2</code></td>
            <td>0/1</td>
            <td>数据位 2</td>
        </tr>
        <tr>
            <td>10</td>
            <td><code>DB3</code></td>
            <td>0/1</td>
            <td>数据位 3</td>
        </tr>
        <tr>
            <td>11</td>
            <td><code>DB4</code></td>
            <td>0/1</td>
            <td>数据位 4</td>
        </tr>
        <tr>
            <td>12</td>
            <td><code>DB5</code></td>
            <td>0/1</td>
            <td>数据位 5</td>
        </tr>
        <tr>
            <td>13</td>
            <td><code>DB6</code></td>
            <td>0/1</td>
            <td>数据位 6</td>
        </tr>
        <tr>
            <td>14</td>
            <td><code>DB7</code></td>
            <td>0/1</td>
            <td>数据位 7</td>
        </tr>
        <tr>
            <td>15</td>
            <td><code>A</code></td>
            <td>-</td>
            <td>背光正极</td>
        </tr>
        <tr>
            <td>16</td>
            <td><code>K</code></td>
            <td>-</td>
            <td>背光负极</td>
        </tr>
    </tbody>
</table>
<p>1 号和 2 号引脚就是元件的接地和正极，后者就接 Arduino 的 +5V 电源引脚；3 号<code>V0</code>为对比度，需要通过一个电位器连接正极和负极来调整。而 15 和 16 号引脚<code>A</code>和<code>K</code>为背光的正极（Anode）和负极（Cathode），为了避免电流可能过大，因此使用了一个 220Ω 的电阻连接到电源上。</p>
<p>由于目前仅测试 LCD 1602 的显示功能，所以 5 号读写控制引脚就直接接地。4 号寄存器选择和 6 号使能分别接 Arduino 的 12，11 号引脚。剩下的数据位就很好办了，直接按从小到大的顺序接 9~2 号引脚。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-04-30-LCD1602/img/Sketch.avif" alt="在 TinkerCAD 中绘制的电路" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>在 TinkerCAD 中绘制的电路</span>
		</figcaption>
	
</figure>
<figure>
	<img src="https://studiountagged.top/articles/2025-04-30-LCD1602/img/IMG_20250430_195609.avif" alt="实物连接图" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>实物连接图</span>
		</figcaption>
	
</figure><p></p>
<p>由于我并没有排线，在实物连接的时候，只能临时通过以购买的杜邦线和跳线来接线，所以看起来就比较凌乱。而且当时不慎将数据位的连接顺序弄反了，还花了一些时间来排错。不过幸运的是，一番折腾之后屏幕终于点亮了。接下来就是写代码使其运行了。</p>
<h2 id="程序编写" tabindex="-1">程序编写</h2>
<p>Arduino 提供了库<code>LiquidCrystal.h</code>用来驱动这类屏幕。然而我并不打算直接使用这个库中预先封装好的各种功能。因为希望能够从更底层开始控制，最终目的是通过 MOS 6502 搭建的 8 位计算机，运行汇编代码并将结果输出到这种屏幕上😎。</p>
<p>为了使其能够正常显示，第一步就是初始化屏幕（Initialize）。根据手册，在 8 位总线模式下的初始化过程如图所示：</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-04-30-LCD1602/img/Initializing_Process.avif" alt="LCD 1602 在 8 位数据模式下的初始化流程图" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>LCD 1602 在 8 位数据模式下的初始化流程图</span>
		</figcaption>
	
</figure><p></p>
<p>根据手册，为了确保 LCD 能够正确识别并进入 8 位总线模式，建议重复三次<code>0x30</code>指令，并每次发送前后都要稍作等待。不过为了简单起见，我就只采用发送一次的方案，信号持续时间 5ms。接下来的几条指令分别是设置显示行数为单/双、字体尺寸、开启显示、清屏、以及输入模式设定。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-04-30-LCD1602/img/Instruction_Table.avif" alt="LCD1602 指令表" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>LCD1602 指令表</span>
		</figcaption>
	
</figure><p></p>
<p>我采用的设置如下：</p>
<table>
    <tbody><tr>
        <th rowspan="2"><span>指令</span><span>Instruction</span></th>
        <th colspan="10"><span>指令码</span><span>Instruction Code</span></th>
        <th rowspan="2" style="text-align: left"><span>描述</span><span>Description</span></th>
    </tr>
    <tr>
        <th><code>RS</code></th>
        <th><code>R/W</code></th>
        <th><code>D7</code></th>
        <th><code>D6</code></th>
        <th><code>D5</code></th>
        <th><code>D4</code></th>
        <th><code>D3</code></th>
        <th><code>D2</code></th>
        <th><code>D1</code></th>
        <th><code>D0</code></th>
    </tr>
    <tr>
        <td><span>功能设置</span><span>Function Set</span></td>
        <td><code>0</code></td>
        <td style="border-right: 2px solid #bfbfbf;"><code>0</code></td>
        <td><code>0</code></td>
        <td><code>0</code></td>
        <td><code>1</code></td>
        <td style="border-right: 2px solid #bfbfbf;"><code>1</code></td>
        <td><code>1</code></td>
        <td><code>1</code></td>
        <td><code>0</code></td>
        <td><code>0</code></td>
        <td>
			<ul>
				<li>8 位总线模式</li>
				<li>双行显示</li>
				<li>5 * 8 字体大小</li>
			</ul>
		</td>
    </tr>
    <tr>
        <td><span>显示控制</span><span>Display ON/OFF Control</span></td>
        <td><code>0</code></td>
        <td style="border-right: 2px solid #bfbfbf;"><code>0</code></td>
        <td><code>0</code></td>
        <td><code>0</code></td>
        <td><code>0</code></td>
        <td style="border-right: 2px solid #bfbfbf;"><code>0</code></td>
        <td><code>1</code></td>
        <td><code>1</code></td>
        <td><code>1</code></td>
        <td><code>1</code></td>
        <td>
			<ul>
				<li>开启显示</li>
				<li>显示光标</li>
				<li>光标闪烁</li>
			</ul>
		</td>
    </tr>
    <tr>
        <td><span>清屏</span><span>Clear Screen</span></td>
        <td><code>0</code></td>
        <td style="border-right: 2px solid #bfbfbf;"><code>0</code></td>
        <td><code>0</code></td>
        <td><code>0</code></td>
        <td><code>0</code></td>
        <td style="border-right: 2px solid #bfbfbf;"><code>0</code></td>
        <td><code>0</code></td>
        <td><code>0</code></td>
        <td><code>0</code></td>
        <td><code>1</code></td>
        <td>
			<ul>
				<li>清屏</li>
			</ul>
		</td>
    </tr>
    <tr>
        <td><span>输入模式</span><span>Entry Mode Set</span></td>
        <td><code>0</code></td>
        <td style="border-right: 2px solid #bfbfbf;"><code>0</code></td>
        <td><code>0</code></td>
        <td><code>0</code></td>
        <td><code>0</code></td>
        <td style="border-right: 2px solid #bfbfbf;"><code>0</code></td>
        <td><code>0</code></td>
        <td><code>1</code></td>
        <td><code>1</code></td>
        <td><code>0</code></td>
        <td>
			<ul>
				<li>光标向右移动（增加）</li>
				<li>整个显示内容不移动</li>
			</ul>
		</td>
    </tr>
</tbody></table>
<p>OK，现在该发送什么指令已经确定了；接下来就是写代码。</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C"><span class="hljs-comment">// LCD1602Tesing.ino</span>

<span class="hljs-meta">#<span class="hljs-keyword">define</span> RS 12 &nbsp;<span class="hljs-comment">// RS 使用引脚 12</span></span>
<span class="hljs-meta">#<span class="hljs-keyword">define</span> E 11 &nbsp; <span class="hljs-comment">// E 使用引脚 11</span></span>

<span class="hljs-comment">// 数据位</span>
<span class="hljs-type">const</span> <span class="hljs-type">int</span> dataPins[<span class="hljs-number">8</span>] = { <span class="hljs-number">9</span>, <span class="hljs-number">8</span>, <span class="hljs-number">7</span>, <span class="hljs-number">6</span>, <span class="hljs-number">5</span>, <span class="hljs-number">4</span>, <span class="hljs-number">3</span>, <span class="hljs-number">2</span> };
</code></pre>
	</div>
</div>

<p>首先定义好 Arduino 上要使用的引脚。</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C"><span class="hljs-comment">// 指令</span>
<span class="hljs-type">const</span> <span class="hljs-type">int</span> funcSet   = <span class="hljs-number">0b00111100</span>; <span class="hljs-comment">// 8 位总线模式，双行显示，5 * 8 字体大小</span>
<span class="hljs-type">const</span> <span class="hljs-type">int</span> dispCtrl  = <span class="hljs-number">0b00001111</span>; <span class="hljs-comment">// 开启显示，隐藏光标，光标不闪烁</span>
<span class="hljs-type">const</span> <span class="hljs-type">int</span> entryMode = <span class="hljs-number">0b00000110</span>; <span class="hljs-comment">// 光标向右移动（增加），整个显示内容不移动</span>
<span class="hljs-type">const</span> <span class="hljs-type">int</span> cls       = <span class="hljs-number">0b00000001</span>; <span class="hljs-comment">// 清屏</span>
</code></pre>
	</div>
</div>

<p>接下来定义要发送的指令。</p>
<p>随后便是重中之重。该如何把这些指令发送到各个引脚上呢？首先要明确写入指令的过程。由于这四条都是指令而非数据，所以<code>RS</code>应设为低电平。</p>
<p>其次就是按位发送指令。而既然提到了按位，逻辑运算肯定是少不了了。这点可以通过<code>for</code>循环从低位引脚开始，通过右移运算依次移动数据，并将该数据的最右侧的一位与数字<code>1</code>进行按位与<code>&amp;</code>运算，<code>0 &amp; 1 =0, 1 &amp; 1 = 1</code>。这样就可以确保结果只会输出一位二进制数据。</p>
<p>最后，设置好各个引脚的电平以后就要在使能引脚上发送一个脉冲以送入数据。根据时序图，一个有效的脉冲时间组成为上升、下降沿时间 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mi>R</mi></msub><mo separator="true">,</mo><msub><mi>t</mi><mi>F</mi></msub></mrow><annotation encoding="application/x-tex">t_{R}, t_{F}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8095em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.0077em;">R</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.1389em;">F</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 加上脉冲宽度（即高电平的时间）<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mi>W</mi></msub></mrow><annotation encoding="application/x-tex">t_{W}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.1389em;">W</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> ，可得 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>t</mi><mo>=</mo><msub><mi>t</mi><mi>R</mi></msub><mo>+</mo><msub><mi>t</mi><mi>F</mi></msub><mo>+</mo><msub><mi>t</mi><mi>W</mi></msub></mrow><annotation encoding="application/x-tex">t=t_{R}+t_{F}+t_{W}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6151em;"></span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.0077em;">R</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.1389em;">F</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.7651em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.1389em;">W</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> 。由于上升、下降沿的时间短到几乎可以忽略不计，所以只要保证 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>t</mi><mo>≥</mo><mn>230</mn><mi>n</mi><mi>s</mi></mrow><annotation encoding="application/x-tex">t\ge230ns</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7719em;vertical-align:-0.136em;"></span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≥</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">230</span><span class="mord mathnormal">n</span><span class="mord mathnormal">s</span></span></span></span> 就足够了。 代码如下：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C"><span class="hljs-comment">// 将数据写入引脚</span>
<span class="hljs-type">void</span> <span class="hljs-title function_">writeByte</span><span class="hljs-params">(<span class="hljs-type">bool</span> rs, byte data, <span class="hljs-type">int</span> delayMs)</span> {
&nbsp; &nbsp; <span class="hljs-comment">// 设置 RS 的电平</span>
&nbsp; &nbsp; <span class="hljs-comment">// 高电平数据模式，低电平指令模式</span>
&nbsp; &nbsp; <span class="hljs-keyword">if</span> (rs)
&nbsp; &nbsp; &nbsp; &nbsp; digitalWrite(RS, HIGH);
&nbsp; &nbsp; <span class="hljs-keyword">else</span>
&nbsp; &nbsp; &nbsp; &nbsp; digitalWrite(RS, LOW);

&nbsp; &nbsp; <span class="hljs-comment">// 将数据写入数据位</span>
&nbsp; &nbsp; <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">8</span>; i++) {
&nbsp; &nbsp; &nbsp; &nbsp; digitalWrite(dataPins[i], (data &gt;&gt; i) &amp; <span class="hljs-number">1</span>);
&nbsp; &nbsp; }

&nbsp; &nbsp; <span class="hljs-comment">// 在使能引脚上生成脉冲</span>
&nbsp; &nbsp; digitalWrite(E, HIGH);
&nbsp; &nbsp; delay(<span class="hljs-number">1</span>);
&nbsp; &nbsp; digitalWrite(E, LOW);

&nbsp; &nbsp; <span class="hljs-comment">// 延迟，确保各个功能执行完毕</span>
&nbsp; &nbsp; delay(delayMs);
}
</code></pre>
	</div>
</div>

<p>这个函数将传入三个参数：<code>rs</code>为布尔值，选择寄存器的模式；<code>byte</code>是<code>Arduino.h</code>中定义的数据类型，其本质就是无符号 8 位整数类型，将传入一个字节大小的命令；整数<code>delayMs</code>为延迟的毫秒数。<code>digitalWrite()</code>也是在<code>Arduino.h</code>中定义的函数，传入的参数为引脚编号和电平值，可为<code>HIGH</code>或者<code>LOW</code>。这样，就可以直接调用这个函数来发送数据了。主程序如下：</p>
<div class="code-card">
	<div class="code-header">
		<span class="code-lang">C</span>
		<button class="code-copy" data-clipboard-target=".code-body pre code" aria-label="复制代码">
			<iconify-icon icon="material-symbols:content-copy-outline-rounded"></iconify-icon>
		</button>
	</div>
	<div class="code-body">
		<pre><code class="hljs language-C"><span class="hljs-type">void</span> <span class="hljs-title function_">setup</span><span class="hljs-params">()</span> {
&nbsp; &nbsp; Serial.begin(<span class="hljs-number">9600</span>);

&nbsp; &nbsp; <span class="hljs-comment">// 设置引脚为输出模式</span>
&nbsp; &nbsp; pinMode(RS, OUTPUT);
&nbsp; &nbsp; pinMode(E, OUTPUT);
&nbsp; &nbsp; 
&nbsp; &nbsp; <span class="hljs-comment">// 设置数据引脚为输出模式</span>
&nbsp; &nbsp; <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">2</span>; i &lt;= <span class="hljs-number">9</span>; i++)
&nbsp; &nbsp; &nbsp; &nbsp; pinMode(i, OUTPUT);

&nbsp; &nbsp; <span class="hljs-comment">// 写入数据</span>
&nbsp; &nbsp; writeByte(<span class="hljs-literal">false</span>, funcSet,   <span class="hljs-number">5</span>); <span class="hljs-comment">// 功能设置</span>
&nbsp; &nbsp; writeByte(<span class="hljs-literal">false</span>, dispCtrl,  <span class="hljs-number">1</span>); <span class="hljs-comment">// 显示控制</span>
&nbsp; &nbsp; writeByte(<span class="hljs-literal">false</span>, cls,       <span class="hljs-number">1</span>); <span class="hljs-comment">// 清屏</span>
&nbsp; &nbsp; writeByte(<span class="hljs-literal">false</span>, entryMode, <span class="hljs-number">1</span>); <span class="hljs-comment">// 输入模式</span>
}

<span class="hljs-type">void</span> <span class="hljs-title function_">loop</span><span class="hljs-params">()</span> {

}
</code></pre>
	</div>
</div>

<p>至此，关于 LCD 1602 的初始化工作就完成了。LCD 1602 在启动时第一排会有一排白色的方块，而如果电路连接以及指令发送正确的话，成功初始化以后是看不见这行方块的。由于现在只是完成初始化了，还没有发送任何要输出的内容，所以屏幕还是一块空白，只有光标在闪烁。关于如何使其显示字符、自定义字符，以及更多复杂操作，将在后续文章中继续进行介绍。</p>
<h2 id="后记" tabindex="-1">后记</h2>
<p>由于笔者这个学期学校里有数字电路的课程，以及下个学期即将到来的单片机，加上平时会观看一些 8-bit 计算机的视频，而网上也有不少通过面包板搭建这样的计算机的视频，自然而然萌生了兴趣；因此便决定趁早开始研究这些东西。笔者并不是那种坐在教室里光听理论的类型，更喜欢的方式是先动手，再根据实际情况中遇到的问题去查资料。整个过程受益颇多，也相信这是未来更深一步学习譬如汇编，以及实现通过 MOS 6502 构建一台能变成的计算机的终极目标的重要基础。</p>
</body></html>
						]]>
					</content:encoded>
				</item>
			

				<item>
					<title>网络信息安全的初窥门径</title>
					<link>https://studiountagged.top/articles/2025-03-26-A-Peek-to-the-Cyber-Security</link>
					<guid isPermaLink="true">https://studiountagged.top/articles/2025-03-26-A-Peek-to-the-Cyber-Security/</guid>
					<pubDate>Tue, 25 Mar 2025 16:00:00 GMT</pubDate>
					<author>Steven Liu</author>
					
					<description>
						<![CDATA[
							初窥网络攻击并对其进行防御的经历。
A peek to and defend against cyber attacks. 
							
						]]>
					</description>
					<content:encoded>
						<![CDATA[
							<html><head></head><body><p>前几日笔者的朋友，也即本工作室的另一成员 Roy 在聊天软件中向我发送了如下图片：</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-03-26-A-Peek-to-the-Cyber-Security/img/lastb-log.avif" alt="lastb 的日志，截图自 Termux" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>lastb 的日志，截图自 Termux</span>
		</figcaption>
	
</figure><p></p>
<p>在搭建本网站的工作中，我主要负责的是前端的设计。因此对于服务器以及后端的各种技术不甚了解。最开始，我并不知道这些文字是什么意思。后来一番询问后才得知，这是服务器登陆失败的记录。当时大吃一惊，因为这些记录密密麻麻的有很多行，几乎每天每时每刻都会有陌生 IP 尝试登陆服务器。</p>
<p>后来经过一番查询才得知，这种行为其实是非常常见的。攻击者会通过一些自动化的扫描工具，例如 masscan, zmap 等对全网进行扫描，以试图找到那些开放了 SSH 端口的服务器，并通过暴力破解尝试登陆。如果服务器采用了弱密码，后果将不堪设想。</p>
<p>虽然本工作室服务器的 root 用户采用的是随机生成的密码，若要暴力破解得花上相当长的时间，不过这也不能代表能就此高枕无忧。为了谨慎起见，我又到网上去了解了一些基本的网络安全知识，并学习了几种应对这类现象的常见防御手段。</p>
<p>第一点便是有针对性地封锁 IP。通过命令<code>lastb | awk '{print $3}' | sort | uniq -c | sort -nr | head -20</code>，可以筛选并排序出 lastb 记录里失败次数排名前 20 的 IP 地址。而<code>fail2ban</code>则会自动监控 SSH 登陆失败的记录，例如相同 IP 连续登陆失败 5 次，对其进行临时封禁。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-03-26-A-Peek-to-the-Cyber-Security/img/Ranking.avif" alt="排名前 20 的攻击者。第一列数字为尝试的次数。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>排名前 20 的攻击者。第一列数字为尝试的次数。</span>
		</figcaption>
	
</figure><p></p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2025-03-26-A-Peek-to-the-Cyber-Security/img/fail2ban.avif" alt="在配置好 fail2ban 之后很快就发挥了它的作用。在写这篇文章的时候仍然有攻击者在尝试登陆服务器，而 fail2ban 不负所望地封禁了这些 IP。" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>在配置好 fail2ban 之后很快就发挥了它的作用。在写这篇文章的时候仍然有攻击者在尝试登陆服务器，而 fail2ban 不负所望地封禁了这些 IP。</span>
		</figcaption>
	
</figure><p></p>
<p>第二种方法是更换 SSH 使用的端口。SSH 默认会使用端口 22，而切换成其他的端口就可以大大减小被自动化扫描的几率。</p>
<p>第三种是在 SSH 连接时采用密钥的方式认证，而非密码。比起传统的密码，这种方式安全性更强。首先可以通过命令<code>ssh-keygen -t rsa -b 4096</code>来生成密钥对，也即是<code>id_rsa.pub（公钥）</code>和<code>id_rsa（私钥）</code>这两个文件。公钥就相当于家里用的密码锁，而私钥就相当于打开这把锁的钥匙。密码锁大家都能看到，但没有正确的钥匙就无法打开。在登陆服务器时，服务器会通过公钥加密一串随机的信息，要求私钥持有者解密这串信息。唯有正确的私钥持有者才能解密出正确的信息，服务器才会允许登陆。</p>
<p>在升级了我们的防御措施之后，我也开始对那些“名列前茅”的 IP 产生了兴趣。又是一番学习后，得知通过 WHOIS 查询可以得知有关该域名的诸多信息。例如，我在<code>lastb</code>记录中发现了有几条 IP 的登陆失败次数完全一致，而且IP地址也高度相似。当时便怀疑攻击者是否是使用了代理服务器或者 VPN 。而在查询后验证了这一猜想，攻击者的确是通过代理服务器发动了分布式攻击。除此之外，通过 ipinfo 还可以进一步查询到该IP精确到街道级的信息。而这一查才发现，攻击者真是来自五湖四海，全球各地。而从登陆失败用的用户名中也可见一斑，其中不乏例如 muhamed 和 ahmed 这类极具地域特色的名字。</p>
<p>这次的经历得以让我对网络安全这一领域初窥门径。以前偶尔会在新闻上听说各式各样某服务器用户数据泄露等等新闻。当时只觉得挺遥远。而现在，这类事件就近在眼前，也促使我开始了解这一领域的一些基础知识。这仅仅是冰山一角，后续随着网站的规模越来越大，要面临的安全问题肯定也不止如此，比如 XSS 注入，DDoS 等等。这篇文章就当作是记录我在这条路上的学习历程和心得感悟吧。</p>
</body></html>
						]]>
					</content:encoded>
				</item>
			

				<item>
					<title>幻象像素衬线体的幕后故事</title>
					<link>https://studiountagged.top/articles/2024-09-18-The-Story-Behind-Illusion-Serif</link>
					<guid isPermaLink="true">https://studiountagged.top/articles/2024-09-18-The-Story-Behind-Illusion-Serif/</guid>
					<pubDate>Fri, 24 May 2024 16:00:00 GMT</pubDate>
					<author>Steven Liu</author>
					
					<description>
						<![CDATA[
							关于踏上字体设计最开始的旅程的故事。
Behind the scenes - the story of how the font design journey began.
							
						]]>
					</description>
					<content:encoded>
						<![CDATA[
							<html><head></head><body><p>「幻象像素衬线体」最早是在 2023 年 4 月 16 日的时候发布于 <a href="https://steven-liu.itch.io" class="external-link" target="_blank">itch.io</a> 的。不过当时并未为其撰写任何文章，最多也就项目主页寥寥数字的介绍。时隔一年，现在终于有些许闲暇时光，便决定为它写点什么，以此来纪念当初制作这款字体时的经历。这篇文章不会花太大的篇幅在介绍字体的特点上，而是会类似于一篇回忆录。所以大家大可当作幕后花絮或者个人访谈来阅读。</p>
<h2 id="开发过程" tabindex="-1">开发过程</h2>
<p>我最开始入坑字体设计的契机是因为独立游戏开发。这款游戏是采用像素画风的。当时为了测试游戏内文本，便找了几款已有的中文像素字体。然而我对这些字体内置的西文字形不甚满意，于是逐渐萌生了自己开发的想法。</p>
<p>在网上搜索了一大圈如何开发后，意外得知了一个叫做 <a href="https://fontstruct.com" class="external-link" target="_blank">FontStruct</a> 的网站。这个网站界面简洁，上手简单、操作便捷（而且还有一点我特别喜欢的是，如果长时间未保存，页面上 Save 这个按钮便会疯狂闪烁，生怕你看不见）。于是，我的字体设计之路便由此开始。从此，这个网站就成了我最常访问的网站。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2024-09-18-The-Story-Behind-Illusion-Serif/img/FontStruct.avif" alt="FontStruct 网站" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>FontStruct 网站</span>
		</figcaption>
	
</figure><p></p>
<p>其次就是 <a href="https://fonts.adobe.com" class="external-link" target="_blank">Adobe Fonts</a>。对于当时对西文书法有些初步了解的我来说，每次参考都能发现一些平时忽略的细节，也逐渐学会了如何从不同的角度欣赏那些经典的作品。</p>
<p>我参考得最多的字体便是 Adobe Garamond, Trajan 以及 Noto Serif。前两者是我最喜欢的西文字体，在后来 Illusion Book 的开发中也是重要参考对象。</p>
<p>Garamond 作为金属活字时代的代表之一，其既兼有人情味和古朴的温润感，同时还能适应现代设计需要。这也是个人在众多数字复刻版本中最喜欢的一版；而 Trajan 这款源自古罗马碑文上的字体，则为后续几千年西文字体的发展奠定了基础。我尤其喜欢这款字体里面 Q 的设计，它那长长的尾巴可以把人迷得神魂颠倒。Noto Serif 的极细字重则在一定程度上帮助我剔除了每个字母外在的筋肉，揭示了内在的骨架。</p>
<p>开发这款字体正好是高三的时候。因此这件事成了每天枯燥、繁重学业以外的小小的放松时光。大部分的开发工作都是在班级的多媒体设备上完成的，有时候还得和班主任和年级组长「打游击战」；或者在实在无法碰到电脑的情况下，先暂时在格子纸上构思一下。这种模式从 2023 年 1 月份开始，一直到 4 月份高考前夕，才完成四种样式的全部设计工作。当时发布的时候也匆匆忙忙，并不知道要怎么宣传，或者说根本无暇宣传，直接在 itch.io 上点了 Publish 按钮就算完事儿了。宣传图也是在高考结束后才制作的。其实现在想来那段时光也挺有意思的，只可惜现在已经回不去了（悲）。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2024-09-18-The-Story-Behind-Illusion-Serif/img/Manuscript.avif" alt="当时的部分设计手稿" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>当时的部分设计手稿</span>
		</figcaption>
	
</figure><p></p>
<h2 id="遇到的问题" tabindex="-1">遇到的问题</h2>
<p>由于当时刚刚接触字体设计，很多基础概念包括字体度量、字偶距等等名词术语看得晕头转向，更别说直接使用 FontForge 这种界面看起来就对新手很不友好的软件了。所以当初发布这款字体时是直接拿 FontStruct 生成的版本。然而为了易于上手，它牺牲了很多更进一步的设置。因此最后导出的版本字体度量数据不正确，用起来有问题。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2024-09-18-The-Story-Behind-Illusion-Serif/img/ExportSample.avif" alt="由 FontStruct 直接导出的字体。字体度量的数据很明显亟须修正" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>由 FontStruct 直接导出的字体。字体度量的数据很明显亟须修正</span>
		</figcaption>
	
</figure><p></p>
<p>除此之外，这个字体家族一共有包括 Regular 在内的四种样式。而 FontStruct 的算法会根据当前字符的大小，自动计算每个单元格的尺寸，这反而导致该字体家族里单元格大小不统一的现象。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2024-09-18-The-Story-Behind-Illusion-Serif/img/FontMetrics.avif" alt="两款字体的单元格尺寸在 FontForge 中的测量结果。左侧字体为 Bold 字重，右侧的为 Regular 字重。单元格的边长明显不一致" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>两款字体的单元格尺寸在 FontForge 中的测量结果。左侧字体为 Bold 字重，右侧的为 Regular 字重。单元格的边长明显不一致</span>
		</figcaption>
	
</figure><p></p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2024-09-18-The-Story-Behind-Illusion-Serif/img/E-mailReply.avif" alt="向作者咨询后得到的回复" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>向作者咨询后得到的回复</span>
		</figcaption>
	
</figure><p></p>
<p>当时在「艰难」地阅读 FontForge 的文档时，得知了一种叫做「标准连字」的 OpenType 特性。这种特性的运用在日常生活中很常见，但在此之前我并不是很清楚这是怎么来的，甚至一度认为是印刷错误导致的。虽然这种一对一的替换规则虽然现在看来再容易理解不过了，然而当时我花了不少时间研究了一堆网页和文章。于是这个便成了第一个我所熟知的特性。这也就是为什么该字体仅包含了两种连字特性。</p>
<p>然而这些功能给我带来的苦恼远不止四处翻找文章那么简单。我兴冲冲地生成了第一批样张后放到 Illustrator 中进行测试，却发现怎么都启用不了。最开始以为是自己有什么地方弄错了，在一遍遍的检查以后，最终都无功而返。这种问题直到我开发 Illusion Book 的时候才真正得到解决——缺少了一个名为<code>aalt</code>的特性。这个特性是我在 ｢解剖｣ 其他字体的时候才得知的。最初不解其意，｢访问全部变体 Access All Alternates｣ 这个名字也叫人看得云里雾里。后来翻阅微软讲排印的手册才得知，所有的替换规则都必须先在这里「登记」一下，排版软件才能正确读取。这也就是为什么我要在 B 站上发讲这些玩意儿的视频。现在正在准备在保留原有字形的基础上，重新绘制轮廓，并修正度量数据以及高级排版功能的信息。</p>
<h2 id="后记" tabindex="-1">后记</h2>
<p>以现在的眼光看来，这个绝对算不上一份完美的作品，无论是从字形上还是技术上都有许多瑕疵。然而，当初对着一个字母反反复复斟酌的心情；以及辛辛苦苦地把 26 个英文字母全都画完，然后再将它们排成句子的时候的那种感觉，我想，应该是自己对字体设计这份热爱的根本源泉。在高考下的教育环境无疑会对学生们自由发展产生一定的扼制；但对我来说，能发掘出新的爱好，也算是意外之喜了。</p>
<p>借由这款字体，我从此便正式入坑了字体设计这一领域，也结识了不少同好。<a href="https://hanshuyuri.itch.io/" class="external-link" target="_blank">HanshuYuri</a> 曾对粗体的字重赞赏有加，并激发他开发了另一款字体（他曾经尝试与我交流，但因在 B 站上发的图文里写错了联络方式这一乌龙，导致迟迟未能联系上）。还是那句老生常谈的话，但是发自内心：感谢大家对我的支持，你们的支持是我前进的动力！</p>
</body></html>
						]]>
					</content:encoded>
				</item>
			

				<item>
					<title>幻象像素衬线体 Illusion Serif</title>
					<link>https://studiountagged.top/articles/2024-09-18-Illusion-Serif</link>
					<guid isPermaLink="true">https://studiountagged.top/articles/2024-09-18-Illusion-Serif/</guid>
					<pubDate>Fri, 24 May 2024 16:00:00 GMT</pubDate>
					<author>Steven Liu</author>
					
					<description>
						<![CDATA[
							像素字体设计的首个作品。
The first work in pixel typefaces designing.
							
						]]>
					</description>
					<content:encoded>
						<![CDATA[
							<html><head></head><body><p></p><figure>
	<img src="https://studiountagged.top/articles/2024-09-18-Illusion-Serif/img/1.avif" alt="" onclick="showModal(this)" loading="lazy">
	
</figure>
<figure>
	<img src="https://studiountagged.top/articles/2024-09-18-Illusion-Serif/img/2.avif" alt="" onclick="showModal(this)" loading="lazy">
	
</figure>
<figure>
	<img src="https://studiountagged.top/articles/2024-09-18-Illusion-Serif/img/3.avif" alt="" onclick="showModal(this)" loading="lazy">
	
</figure><p></p>
<p>开源的像素风格衬线体，目前支持拉丁语增补，拉丁语扩充-A，部分希腊语和科普特语以及部分西里尔文。该字体家族包含常规和粗体两种字重，常规和斜体两种样式。支持例如 fi, ffi, fl, ffl 等连字以及 ct, st, sp 的自由连字。</p>
<h2 id="技术指标" tabindex="-1">技术指标</h2>
<p>压缩包中将包含 OTF, TTF, WOFF 和 WOFF2 四种格式的字体。与此同时还包含了 README 和 LICENSE 两则说明文件。</p>
<p>该字体的 UPM 值为 1200。若想取得最佳显示效果，字号应基于 12px 的大小进行整数倍的缩放。</p>
<p>请注意，由于本字体在字距调整时并未完全采用整数倍单元格大小的数值，这可能导致在某些情况下字体不能正确地渲染。在允许的情况下，请关闭任何的抗锯齿设置以及过滤模式，并将字距微调和追踪皆设为 0。</p>
<h2 id="授权及获取方式" tabindex="-1">授权及获取方式</h2>
<p>该字体采用的是 SIL 开源字体协议。</p>
<p>你可以在直接在本页面上或 <a href="https://steven-liu.itch.io/illusionserif" class="external-link" target="_blank">itch.io</a> 上下载已经封装好的字体文件，也可以直接在 GitHub 上下载该字体的 FontForge 工程文件。</p>
<h2 id="鸣谢" tabindex="-1">鸣谢</h2>
<p>整个字体家族的开发是在高三这一年中完成的。首先得感谢我的老师们，他们虽然并未对此字体的制作做出贡献，不过正是因为有了他们的悉心指导，我才能在高考中超越自我，考取了一个能让自己满意的成绩。</p>
<p>感谢我的父母。他们在这一年里作为我的坚强后盾，帮我顺利地度过了高考难关。他们也一如既往地支持着我的爱好。</p>
<p>感谢 <a href="https://hanshuyuri.itch.io/" class="external-link" target="_blank">HanshuYuri</a> 对我的肯定。他对此字体的粗体赞赏有加，我们俩人因此相识。没有什么比自己的作品受到他人赞赏这一点更令人振奋了，而且还找到了同好。</p>
<p>感谢我的挚友 Roy, Heinrich 和摇曳对我在呈现这款字体效果方面的协助，他们能不厌其烦的听我在这方面的「高谈阔论」，并提出适当的建议。</p>
<p>关于此字体开发的幕后故事，请阅读<a href="/articles/2024-09-18-The-Story-Behind-Illusion-Serif">此文章</a>。</p>
</body></html>
						]]>
					</content:encoded>
				</item>
			

				<item>
					<title>幻象像素书体 Illusion Book</title>
					<link>https://studiountagged.top/articles/2024-06-01-Illusion-Book</link>
					<guid isPermaLink="true">https://studiountagged.top/articles/2024-06-01-Illusion-Book/</guid>
					<pubDate>Fri, 31 May 2024 16:00:00 GMT</pubDate>
					<author>Steven Liu</author>
					
					<description>
						<![CDATA[
							一款尝试在像素世界中寻铅字时代的温润的字体。
A font tries to find the warmth of the typeface era in a world of pixels.
							
						]]>
					</description>
					<content:encoded>
						<![CDATA[
							<html><head></head><body><p>「幻象像素书体」是继幻象像素衬线体后，独立设计的第二款衬线风格的像素字体。经过半年多的设计与打磨后，完成了以西文为主的支持。目前其他例如中日韩的字符集仍在开发中。</p>
<h2 id="设计" tabindex="-1">设计</h2>
<p>两者在风格上很相似，甚至有些字母的字形是完全一致的。因为它们的设计理念都是希望能够在像素风格上，展现衬线体所特有的优雅，同时还要有高可读性，适用于大段文本。</p>
<p>然而，这款字体最显著的变化便是缩小了字怀。小巧的字怀使文字的排列更加紧致，灰度更加均匀，同时不失节奏感。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2024-06-01-Illusion-Book/img/1.avif" alt="Illusion Book 和 Illusion Serif 的对比" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>Illusion Book 和 Illusion Serif 的对比</span>
		</figcaption>
	
</figure><p></p>
<p>字形也并未设计成等宽的样式，因为有些字母强行拉宽会显得十分拖沓；而有的强行压缩则又会拥挤不堪。在设计时大量参考了传统的旧式字体，例如罗马大写体，对每个大写字母的宽度进行了分类，并以此为依据进行设计。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2024-06-01-Illusion-Book/img/2.avif" alt="大写字母的设计" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>大写字母的设计</span>
		</figcaption>
	
</figure><p></p>
<p>在比例上，整套字体基于 12px 的尺寸进行设计。升部的高度为 9px，降部的高度为 3px。大写字母的高度被特意设计成比升部矮一个像素；而 6px 高的小写字母既不显得过于狭长，也不矮胖。</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2024-06-01-Illusion-Book/img/3.avif" alt="字体度量" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>字体度量</span>
		</figcaption>
	
</figure><p></p>
<h2 id="高级排版特性" tabindex="-1">高级排版特性</h2>
<p>虽然看上去是像素的，但并不是老旧的像素字体格式，所有的「像素」其实都是由矢量轮廓描摹出来的，除此之外，它还配备了丰富的高级排版武器，即 OpenType 特性。在专业的平面设计和排版软件（比如 Adobe InDesign 和 Illustrator）以及网页开发中，它能够让文字以更加灵活的方式呈现。</p>
<p>比如，按照西方排版惯例，自动将一些字母连写的合字（liga­ture）功能：</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2024-06-01-Illusion-Book/img/4.avif" alt="连字特性" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>连字特性</span>
		</figcaption>
	
</figure><p></p>
<p>以及为了配合小写字母的旧式数字（old-style figures）：</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2024-06-01-Illusion-Book/img/5.avif" alt="旧式数字" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>旧式数字</span>
		</figcaption>
	
</figure><p></p>
<p>还有小型大写字母（small cap­itals）：</p>
<p></p><figure>
	<img src="https://studiountagged.top/articles/2024-06-01-Illusion-Book/img/5.avif" alt="小型大写字母" onclick="showModal(this)" loading="lazy">
	
		<figcaption>
			<span>小型大写字母</span>
		</figcaption>
	
</figure><p></p>
<p>除了字形以外，字符间距也经过了仔细推敲。为了美观，在制作这款字体时毅然放弃了以整数倍单元格宽度进行调整的思路，就是为了避免在某些情况下会出现「不调整看上去不太对劲，调整了又更不对劲」的尴尬地步。以 1/4 单元格宽度为基础，对大量的字偶对进行了手动微调。</p>
<h2 id="技术指标" tabindex="-1">技术指标</h2>
<p>压缩包中将包含 OTF, TTF, WOFF 和 WOFF2 四种格式的字体。与此同时还包含了 README 和 LICENSE 两则说明文件。</p>
<p>该字体的 UPM 值为 1200。若想取得最佳显示效果，字号应基于 12px 的大小进行整数倍的缩放。</p>
<p>请注意，由于本字体在字距调整时并未完全采用整数倍单元格大小的数值，这可能导致在某些情况下字体不能正确地渲染。在允许的情况下，请关闭任何的抗锯齿设置以及过滤模式，并将字距微调和追踪皆设为 0。</p>
<h2 id="授权及获取方式" tabindex="-1">授权及获取方式</h2>
<p>该字体采用的是 <a href="https://openfontlicense.org/" class="external-link" target="_blank">SIL 开源字体协议</a>。</p>
<p>你可以在直接在本页面上或 <a href="https://steven-liu.itch.io/illusion-book" class="external-link" target="_blank">itch.io</a> 上下载已经封装好的字体文件，也可以直接在 <a href="https://github.com/StevenLZH/IllusionBook" class="external-link" target="_blank">GitHub</a> 上下载该字体的 FontForge 工程文件。</p>
<h2 id="鸣谢" tabindex="-1">鸣谢</h2>
<p>感谢 Roy, Heinrich 和摇曳对我在呈现这款字体效果方面的协助，特别感谢 Roy 在技术支持方面的帮助；同时，也要感谢 <a href="https://hanshuyuri.itch.io/" class="external-link" target="_blank">HanshuYuri</a> 对字体的宝贵建议，他的像素字体也非常出色。没有他们的支持，这款字体可能无法达到目前的效果。</p>
<p>感谢我的家人对我的爱好的支持。他们开明包容的态度是我走上这条道路的基础；</p>
<p>感谢 B 站上我的粉丝们对我的认可，以及 QQ 群“像素字体工房”的各路大佬分享的技术与心得。</p>
</body></html>
						]]>
					</content:encoded>
				</item>
			

				<item>
					<title>幻象像素书体 Illusion Book - 正式发布！</title>
					<link>https://studiountagged.top/articles/2024-05-25-The-Release-of-Illusion-Book</link>
					<guid isPermaLink="true">https://studiountagged.top/articles/2024-05-25-The-Release-of-Illusion-Book/</guid>
					<pubDate>Fri, 24 May 2024 16:00:00 GMT</pubDate>
					<author>Steven Liu</author>
					
					<description>
						<![CDATA[
							一款尝试在像素世界中寻铅字时代的温润的字体。
A font tries to find the warmth of the typeface era in a world of pixels.
							
						]]>
					</description>
					<content:encoded>
						<![CDATA[
							<html><head></head><body><p></p><figure>
	<img src="https://studiountagged.top/articles/2024-05-25-The-Release-of-Illusion-Book/img/A_Study_In_Scarlet.avif" alt="" onclick="showModal(this)" loading="lazy">
	
</figure>
<figure>
	<img src="https://studiountagged.top/articles/2024-05-25-The-Release-of-Illusion-Book/img/Inscription.avif" alt="" onclick="showModal(this)" loading="lazy">
	
</figure>
<figure>
	<img src="https://studiountagged.top/articles/2024-05-25-The-Release-of-Illusion-Book/img/Text-06-06.avif" alt="" onclick="showModal(this)" loading="lazy">
	
</figure><p></p>
<p>Illusion&nbsp;Book 幻象像素书体 是一款基于 12*12 px 大小的像素字体。该字体希望能够在像素风格上，展现衬线体所特有的优雅，同时还要有高可读性，适用于大段文本。</p>
<p>在经过差不多半年时间的设计与打磨之后，这款字体终于在今天正式发布了！立即下载并体验吧！</p>
<p>该字体采用了 <a href="https://openfontlicense.org/" class="external-link" target="_blank">SIL 开源字体协议</a>，你可以免费将其用于个人，非商业乃至商业目的。</p>
<p>字体文件可以戳本页面内的下载按钮；也可以参见 <a href="https://steven-liu.itch.io/illusion-book" class="external-link" target="_blank">itch.io</a> 项目主页；字体的 FontForge 工程文件也已公开至 <a href="https://github.com/StevenLZH/IllusionBook" class="external-link" target="_blank">GitHub</a>。</p>
<p>如果你喜欢这款字体的话，请在 <a href="https://afdian.com/a/steven-liu" class="external-link" target="_blank">爱发电</a> 或 <a href="https://patreon.com/StevenLiu" class="external-link" target="_blank">Patreon</a> 上支持我吧！</p>
<p>更多信息请阅读 <a href="/articles/2024-06-01-Illusion-Book">此文章</a>。</p>
</body></html>
						]]>
					</content:encoded>
				</item>
			
	</channel>
</rss>