用Microbit模拟环形振荡器:从逻辑门到时钟信号的数字电路实践

Microbit环形振荡器时钟信号
于 2026-06-01 13:05:41 修改
·本内容遵循CC 4.0 BY-SA版权协议

1. 项目概述:用Microbit玩转数字电路的心脏

如果你对数字电路或者嵌入式开发感兴趣,一定听说过“时钟信号”这个概念。它是整个数字系统协调工作的节拍器,从你手机里的处理器到路由器里的芯片,都离不开一个稳定可靠的时钟源。今天,我们不谈那些复杂的晶振或PLL芯片,就来聊聊一个最基础、最纯粹,却也最能揭示数字电路本质的时钟生成电路——环形振荡器。

环形振荡器的原理简单得令人着迷:它只需要奇数个“非门”(NOT Gate),把它们首尾相连成一个环。由于非门的特性是“输入为高,输出就为低;输入为低,输出就为高”,当这个环形成后,信号就会在这个环里不断地自我翻转、循环奔跑,从而产生一个持续的方波信号。这个方波的频率,直接取决于信号跑完一圈所需要的时间,也就是所有非门延迟时间的总和。所以,从本质上讲,环形振荡器是把数字逻辑门固有的、微小的“反应时间”(传播延迟)巧妙地利用起来,转化成了我们需要的周期性信号。

那么,为什么用Microbit来做这件事呢?对于初学者或教育场景,直接搭建物理的74系列逻辑门电路虽然直观,但需要面包板、连线、电源,调试起来也麻烦。而Microbit这类微控制器,本质上就是一个可编程的数字系统,它的GPIO引脚可以模拟数字输入输出,其程序执行时间可以精确控制,从而完美地“模拟”出逻辑门及其延迟。这就像是在软件世界里搭建了一个虚拟的数字电路实验室,安全、灵活且成本极低。通过这个项目,你不仅能亲手实现一个振荡器,更能深刻理解“软件如何定义硬件行为”以及“时序”在数字系统中的核心地位。无论你是电子爱好者、学生,还是想巩固底层知识的嵌入式开发者,这个从逻辑门到频率控制的实践之旅,都会让你对数字世界的脉搏有更直接的感触。

2. 核心原理与设计思路拆解

2.1 环形振荡器的工作原理:为什么必须是奇数个门?

要理解环形振荡器,必须从非门(反相器)的基本特性说起。非门是一个具有单一输入和单一输出的逻辑器件,它的功能是逻辑取反:输入为逻辑0(低电平),则输出为逻辑1(高电平);输入为逻辑1,则输出为逻辑0。理想情况下,这个翻转是瞬间完成的。但在现实中,任何物理器件都存在“传播延迟”——信号从输入发生变化到输出产生相应变化所需要的时间,记作 Td。

当我们把奇数个(N个)非门串联起来,并将最后一个门的输出反馈回第一个门的输入,就构成了一个闭环,如下图所示(概念示意):

TEXT
输入 -> [非门1] -(Td)-> [非门2] -(Td)-> ... -> [非门N] -(Td)-> 输出
^ |
|_________________________________________________________|

现在,让我们追踪一个逻辑跳变在这个环中的旅行。假设在某个初始时刻,第一个非门的输入为0。经过一个Td的延迟后,它的输出变为1。这个1作为第二个非门的输入,再经过一个Td,第二个非门的输出变为0……如此传递下去。经过 N*Td 的总时间后,这个逻辑跳变(从0到1的变化)会传递回起点,即第一个非门的输入。关键点来了:因为经历了奇数(N)次取反,回到起点的信号状态与最初的状态相反(最初是0,回来的是1)。这个新的输入状态(1)又会触发新一轮的翻转,使输出变为0,这个0再经过N个门后,又会以0的状态回来(因为0经过奇数次取反变1,再经过奇数次取反又变回0?这里需要仔细推演)。

实际上,更严谨的分析是考虑环路的稳态。由于环路增益大于1且相移为180度(奇数个反相器提供180度相移,加上环路本身的180度相移,满足振荡的巴克豪森准则),电路无法稳定在任何一个静态逻辑电平上,只能进入持续振荡状态。信号在环中每循环一圈,其电平就翻转一次。一圈的行程时间是 N*Td,而一次完整的翻转(从高到低再到高,或从低到高再到低)需要信号在环中跑两圈(因为跑一圈后电平相反,需要再跑一圈才能回到初始电平)。因此,输出方波的周期 T = 2 * N * Td。

由此,我们得到振荡频率的公式: 频率 F = 1 / T = 1 / (2 * N * Td)

这就是为什么必须是奇数个门。如果是偶数个门,信号绕一圈回来经历了偶数次取反,状态与初始状态相同,电路会迅速稳定在某个逻辑电平上(可能是亚稳态,但最终会趋于一个固定值),而无法形成持续振荡。

2.2 用Microbit模拟非门与延迟:软件定义硬件

在物理电路中,Td是由半导体工艺、温度、电压等因素决定的固有属性,通常为纳秒级别,且难以在成品芯片中大幅调整。而用Microbit模拟,则给我们带来了极大的灵活性。

1. 非门功能的软件实现: Microbit的非门模拟非常简单。我们使用一个GPIO引脚作为“输入”,另一个GPIO引脚作为“输出”。在程序的主循环中,不断执行以下操作:

  1. 读取输入引脚的电平(digital read)。
  2. 对这个电平值进行逻辑“非”运算(如果读到的值是1,则准备输出0;如果是0,则准备输出1)。
  3. 将运算结果写入输出引脚(digital write)。

这个过程本身就引入了一个延迟,即执行一次“读取-计算-写入”循环所需的时间。这个时间就是我们在软件中可控的 Td

2. 延迟时间的精确控制: Microbit的微控制器(通常是ARM Cortex-M系列)运行速度很快,一次简单的读取-写入循环可能只需要几微秒,这会产生很高的振荡频率(几百kHz甚至MHz),远超我们肉眼或简单仪器能观察的范围。因此,我们需要在循环中主动加入延迟,来获得我们想要的、更低的频率(如1Hz到几十Hz)。

加入延迟的方法通常是调用类似 sleep_ms()delay() 的函数,或者执行一个空循环来“浪费”时间。通过调整这个延迟的时长,我们就可以直接控制公式中的 Td,从而精确设定输出频率。例如,想要一个1Hz的振荡器(周期1秒),假设我们使用1个非门(N=1),根据公式 T = 2 * 1 * Td = 1秒,那么我们需要设置的 Td = 0.5秒。这意味着我们的代码循环一次(完成一次非操作)需要耗时500毫秒。

3. 单门振荡与多级振荡的考量: 原项目提到了使用单个非门。根据公式,当N=1时,F = 1/(2Td)。这意味着频率只由软件延迟Td决定。这是最简单的实现方式。 理论上,我们也可以在代码中模拟多个串联的非门(例如,用多个“读取-取反-写入”的代码段顺序执行,每个段之间可以有不同的延迟),这样N就大于1。增加N会降低频率,或者在相同频率下允许使用更短的软件延迟(因为Td = 1/(2N*F))。但在Microbit上,模拟多级门在代码上会更复杂,且由于所有“门”都在同一个处理器上串行执行,其总延迟与使用一个门但延迟时间设为N倍Td是等价的。因此,从教学和简洁性角度,使用单个非门并调整延迟时间是最直观有效的方法。

2.3 硬件连接与信号观测方案

尽管核心逻辑在软件中,但我们仍需要简单的硬件连接来完成闭环并观测结果。

1. 闭环连接: 这是最关键的一步。你需要一根杜邦线(或任何导线),将Microbit上被定义为“输出”的那个引脚,直接连接到被定义为“输入”的那个引脚。这就构成了物理上的反馈环,将软件非门的输出送回了它的输入。没有这根线,代码就只是一个不断读取某个固定输入(可能是浮空状态,不确定)并输出其反相值的程序,不会自激振荡。

2. 频率计算与测量点:

  • 计算:根据你代码中设置的延迟时间(Td),利用公式 F = 1/(2Td) 即可计算出理论频率。例如,若 delay(500) 表示延迟500毫秒,则 Td = 0.5秒,理论频率 F = 1/(20.5) = 1 Hz。
  • 测量点:你可以从“输出”引脚连接信号到示波器或逻辑分析仪,直接观察生成的方波波形,测量其周期和频率,与理论值对比。这是最准确的验证方式。

3. 可视化反馈(用于无仪器情况): 为了方便在没有专业仪器的情况下观察振荡,我们可以利用Microbit的LED点阵或串口输出。

  • LED指示:在代码中,让每次输出电平变化时,切换一个LED(如中央的LED)的亮灭。这样,LED的闪烁频率就是方波频率的一半(因为亮和灭各对应半个周期)。观察LED的闪烁,可以直观感受频率的变化。
  • 串口打印:可以在每次循环中通过串口打印当前的输出电平值或时间戳,然后在电脑的串口监视器上观察文本输出的变化节奏,这也是一种定性的观测方法。

注意:GPIO引脚配置 在连接导线前,务必在代码初始化部分正确设置引脚的输入/输出模式。输出引脚应设置为OUTPUT,输入引脚应设置为INPUT。对于这种自反馈连接,由于输出直接驱动输入,输入引脚不需要上拉或下拉电阻。

3. 代码实现与关键参数解析

我们将使用MicroPython来为Microbit V2编写代码(V1同样适用)。整个项目的代码结构非常清晰,核心就是一个包含了延迟的循环。

3.1 基础单非门振荡器代码实现

PYTHON
# 导入必要的库
from microbit import *
import utime
 
# 引脚定义
INPUT_PIN = pin0 # 假设使用pin0作为输入引脚
OUTPUT_PIN = pin1 # 假设使用pin1作为输出引脚
LED_INDICATOR = (2, 2) # 使用点阵中央的LED作为指示灯
 
# 初始化引脚模式
INPUT_PIN.read_digital() # 先读取一次,确保模式正确(对于microbit,设置输入模式的方式)
# 注意:Microbit的Pin对象在创建时通常已隐含模式,直接使用read/write即可。
# 更明确的设置可以使用:INPUT_PIN.set_pull(pin.NO_PULL) (如果需要)
# OUTPUT_PIN.write_digital(0) # 初始化为低电平
 
# 核心振荡循环
while True:
# 1. 读取输入电平
input_state = INPUT_PIN.read_digital()
# 2. 计算非逻辑(取反)
# 在MicroPython中,read_digital()返回0或1。
output_state = 0 if (input_state == 1) else 1
# 3. 写入输出电平
OUTPUT_PIN.write_digital(output_state)
# 4. 根据输出状态控制LED(可视化)
display.set_pixel(LED_INDICATOR[0], LED_INDICATOR[1], output_state * 9) # 输出为1时LED最亮,为0时熄灭
# 5. 关键:插入延迟,控制振荡频率
# delay_ms = 500 # 延迟500毫秒,对应理论频率1Hz
delay_ms = 20 # 延迟20毫秒,对应理论频率约25Hz (1/(2*0.020))
sleep(delay_ms) # microbit的sleep单位是毫秒

代码逐行解析:

  1. 引脚定义:选择了pin0和pin1。你可以根据手头连接线的方便程度选择任何一对数字IO引脚(如pin0-pin1, pin2-pin3等),只要确保在物理上用导线将输出引脚连回输入引脚。
  2. 循环体while True 构成了一个无限循环,模拟了数字电路持续工作的特性。
  3. 读取与取反INPUT_PIN.read_digital() 读取输入引脚当前的数字电平(0或1)。随后用三元表达式进行逻辑取反。
  4. 输出与可视化OUTPUT_PIN.write_digital() 将取反后的值输出。同时,通过 display.set_pixel 将输出状态映射到LED点阵上,高电平时LED亮起,低电平时熄灭,提供了直观的视觉反馈。
  5. 核心延迟sleep(delay_ms) 是控制频率的关键。它暂停程序的执行,从而人为地增加了“非门”的传播延迟 Td。

3.2 频率计算与参数调整实践

根据公式 F = 1 / (2 * Td),其中 Td 是循环一次的总时间(包括读取、计算、写入和sleep的时间)。由于读取、计算、写入的操作在微控制器上执行极快(微秒级),当sleep时间在几十毫秒以上时,这些操作时间可以忽略不计。因此,Td ≈ delay_ms / 1000 秒。

  • 目标频率为1Hz

    • 周期 T = 1/F = 1秒。
    • 由于 T = 2 * Td,所以 Td = T / 2 = 0.5秒 = 500毫秒。
    • 将代码中的 delay_ms 设置为 500。
    • 此时,中央LED会以1秒为周期(亮0.5秒,灭0.5秒)闪烁。
  • 目标频率为25Hz(接近Microbit V2默认示例频率)

    • T = 1/25 = 0.04秒 = 40毫秒。
    • Td = T / 2 = 20毫秒。
    • delay_ms 设置为 20。
    • LED的闪烁会非常快,人眼几乎分辨不出闪烁,感觉像是持续亮着但略有暗淡。
  • 参数调整实验: 你可以创建一个变量列表来尝试不同的延迟值,观察现象:

    PYTHON
    delays_to_try = [1000, 500, 200, 100, 50, 20, 10] # 单位毫秒
    for d in delays_to_try:
    display.scroll(str(d)) # 显示当前尝试的延迟值
    sleep(1000)
    # 然后运行一段时间的振荡器
    start_time = utime.ticks_ms()
    while utime.ticks_diff(utime.ticks_ms(), start_time) < 5000: # 运行5秒
    # ... 振荡器循环代码,使用变量 d 作为延迟 ...
    input_state = INPUT_PIN.read_digital()
    output_state = 0 if (input_state == 1) else 1
    OUTPUT_PIN.write_digital(output_state)
    display.set_pixel(2, 2, output_state * 9)
    sleep(d) # 使用变量d
    display.clear()

    通过这个实验,你可以清晰地看到延迟时间(Td)与振荡频率(LED闪烁快慢)之间的反比关系,直观验证理论公式。

3.3 代码优化与稳定性探讨

基础代码虽然能工作,但在实际实验中可能会遇到一些不稳定现象,比如LED闪烁不均匀,或者测量到的频率与理论值有偏差。这通常由以下原因造成:

1. 循环时间的不精确性: sleep() 函数提供的延迟是近似值,它会让程序暂停至少指定的毫秒数,但精确度受系统调度和其他后台任务(如Microbit的蓝牙栈、系统计时器)的影响。对于要求不高的低频振荡(如1Hz),这通常可以接受。但对于更高频率或更精确的需求,就需要更精细的时间控制。

优化方案:使用 utime.ticks_us() 进行忙等待

PYTHON
import utime
 
def delay_microseconds(us):
start = utime.ticks_us()
while utime.ticks_diff(utime.ticks_us(), start) < us:
pass # 忙等待,占用CPU
 
while True:
input_state = INPUT_PIN.read_digital()
output_state = 1 - input_state # 另一种取反写法
OUTPUT_PIN.write_digital(output_state)
display.set_pixel(2, 2, output_state * 9)
delay_microseconds(20000) # 延迟20000微秒,即20毫秒

这种方法通过高精度计时器进行“忙等待”,能提供比 sleep() 更精确的延迟,尤其适用于较短的延迟时间。但缺点是CPU占用率100%。

2. 输入信号稳定与防抖动: 由于输出直接通过导线连接回输入,理论上信号是干净的。但在实际中,导线可能引入噪声,或者在电平切换的瞬间,输入引脚可能会读取到一个不稳定的中间值(虽然概率很低)。为了代码健壮性,可以考虑在读取输入后进行一次简单的验证或多次采样取平均值(对于数字信号,通常取连续两次读取相同值)。

3. 使用外部中断的进阶思路(可选): 一个更接近硬件非门工作方式的模拟是使用外部中断。可以将输入引脚配置为中断触发模式,当输入电平变化时,自动触发中断服务程序,在中断中立即取反并输出。这样,延迟Td就主要由中断响应时间和代码执行时间决定,更加“即时”。但这部分代码更复杂,且中断处理不当可能引入其他时序问题,适合进阶探索。

实操心得: 在最初测试时,我建议先使用最简单的 sleep() 方案,并选择较长的延迟(如500毫秒),确保电路和代码基本工作。在成功观察到稳定的1Hz闪烁后,再逐步减少延迟,尝试更高的频率,并切换到更精确的定时方法。同时,用手机慢动作摄影功能拍摄LED闪烁,然后逐帧查看,是测量低频周期的一个非常实用的土办法。

4. 硬件搭建、测试与问题排查

4.1 分步搭建指南与安全注意事项

所需材料清单:

  1. Microbit开发板(V1或V2均可)一块。
  2. 微型USB数据线一条(用于供电和编程)。
  3. 杜邦线(母对母)至少一根。
  4. (可选)万用表或示波器/逻辑分析仪,用于精确测量。
  5. (可选)电脑一台,用于编写和上传代码。

搭建步骤:

  1. 软件准备:访问Microbit官方编程网站(如Python Editor),将上一章节的代码复制进去,或者使用Mu Editor等离线工具。根据你想要的频率,修改 delay_ms 变量的值(例如,先设置为500对应1Hz)。将程序下载为.hex文件,并通过USB线拖入MICROBIT盘符,完成烧录。

  2. 物理连接

    • 找到你代码中定义的 OUTPUT_PININPUT_PIN 对应的物理引脚。例如,如果代码用的是 pin1 输出,pin0 输入,那么找到Microbit边缘连接器上标有“1”和“0”的焊盘或引脚。
    • 用一根杜邦线,将输出引脚(如pin1)和输入引脚(如pin0)直接短接起来。 这是形成振荡环路的唯一必要连接。
    • 确保连接牢固,避免虚接。
  3. 上电与观察

    • 通过USB线给Microbit上电。
    • 观察Microbit的5x5 LED点阵。你应该能看到位于(2,2)的中央LED开始以设定的频率闪烁。
    • 如果LED常亮、常灭或不规则闪烁,请进入排查环节。

安全与注意事项:

  • 静电防护:虽然Microbit相对耐用,但处理时尽量避免直接触摸芯片和裸露的电路,尤其是在干燥环境下。
  • 短路风险:确保杜邦线只连接了指定的两个引脚,没有触碰到其他引脚(尤其是3V和GND),防止短路。
  • 电流驱动:Microbit的GPIO引脚驱动能力有限(约5mA)。本项目是输出连接回输入,输入阻抗很高,几乎不消耗电流,所以完全在安全范围内。切勿用这个输出引脚直接驱动电机、大功率LED等负载。
  • 代码修改安全:在修改延迟参数时,避免设置极短的延迟(如小于1毫秒)。这可能导致循环频率过高,消耗大量CPU资源,虽然不会损坏硬件,但可能导致系统响应变慢,LED显示异常。

4.2 测试方法与频率验证

1. 定性测试(目测LED): 这是最直接的方法。设定一个较长的延迟(如500ms),观察中央LED是否规律地亮灭。用手机的秒表功能,计时LED闪烁10个周期所需的时间,然后除以10得到平均周期,再取倒数得到频率。与理论值(1/(2*0.5)=1Hz)对比。

2. 定量测试(使用万用表): 将万用表打到直流电压档,红表笔接触输出引脚(如pin1),黑表笔接触Microbit的GND引脚。当振荡器工作时,你会看到电压值在0V和~3V之间周期性跳动。对于低频(如1Hz),万用表读数会缓慢地在0和3左右切换。对于较高频率,万用表可能显示一个中间值(平均电压),例如50%占空比的方波,平均电压约为1.5V。这可以证明方波的存在,但无法精确测频。

3. 精确测试(使用示波器或逻辑分析仪): 这是最佳方法。

  • 将示波器探头的信号钩连接到输出引脚,地线夹到Microbit的GND。
  • 调整示波器的时基和电压刻度。
  • 你应该能看到一个清晰的方波波形。
  • 使用示波器的测量功能,直接读取波形的频率和周期。
  • 对比理论值与实测值:记录下你代码中设置的 delay_ms 值,计算理论频率 F_theory = 1000 / (2 * delay_ms) Hz。在示波器上读取实测频率 F_measured。两者通常会有些许差异。

4.3 常见问题排查速查表

下表列出了搭建和测试过程中可能遇到的问题、原因及解决方法:

现象 可能原因 排查步骤与解决方案
LED完全不亮或完全常亮 1. 反馈环路未闭合。
2. 代码未成功烧录。
3. 引脚定义错误。
4. 杜邦线损坏或接触不良。
1. 检查连线:确保输出引脚确实用导线连接到了输入引脚。这是最常见错误。
2. 验证程序:重新烧录程序,尝试让Microbit显示一个简单的图案(如display.show(Image.HAPPY))以确认程序运行正常。
3. 核对引脚:检查代码中的INPUT_PINOUTPUT_PIN变量值与实际连接的物理引脚是否一致。Microbit的pin0, pin1, pin2等是通用的。
4. 更换导线:换一根杜邦线试试。
LED闪烁频率明显不对(如比理论值慢很多或快很多) 1. 延迟时间计算或设置错误。
2. 代码中除了sleep还有额外耗时操作。
3. sleep函数精度问题。
1. 复查公式:确认Td是sleep的时间(秒)。频率F=1/(2*Td)。
2. 简化代码:移除LED显示等非核心代码,只保留读、反、写、延迟四步,再测试频率。
3. 使用更精确延时:如4.3节所述,尝试使用utime.ticks_us进行忙等待延迟,对比结果。
LED闪烁不稳定(周期时长时短) 1. 电源不稳定。
2. 微控制器被其他任务中断(如系统后台任务)。
3. 导线接触不良,引入噪声。
1. 检查电源:使用电脑USB口或可靠的手机充电器供电,避免使用电量不足的移动电源。
2. 关闭无线功能:如果用的是Microbit V2,且不需要,可以尝试在代码开头禁用蓝牙radio.off(),减少系统干扰。
3. 确保连接可靠:按压一下杜邦线接头,或更换连接点。
示波器上看不到干净方波(波形畸变、毛刺) 1. 探头接地不良。
2. 示波器带宽或设置不当。
3. Microbit GPIO输出上升/下降时间本身有限(非理想方波)。
1. 检查探头接地:确保示波器地线夹紧在Microbit的GND上,且接地回路尽量短。
2. 调整示波器:尝试调整触发电平,使用带宽限制功能滤除高频噪声。
3. 理解现实器件:微控制器引脚输出不是理想的瞬时跳变,会有纳秒级的上升/下降时间,在低频下观察不到,在很高频率下才会显现。本项目频率很低,这通常不是问题。
改变延迟参数,频率无变化 1. 代码修改后未重新烧录。
2. 变量名拼写错误,实际延迟未改变。
3. 延迟值太小,被循环其他部分的时间主导。
1. 重新烧录:任何代码修改后,必须重新生成.hex文件并拖入MICROBIT盘符。
2. 检查代码:仔细查看sleep语句中的变量或数值是否已更新。
3. 增加延迟:尝试一个非常大的延迟值(如2000毫秒),看LED闪烁是否明显变慢,以确认延迟是否生效。

一个关键的排查技巧: 如果一切连接正确但电路不振荡,可以尝试一个简单的“开环测试”。暂时断开输出到输入的反馈线。在代码中,将INPUT_PIN.read_digital()改为一个固定值(如input_state = 0)。这样,输出应该会持续输出高电平(1),LED常亮。然后手动将输入引脚(通过杜邦线)瞬间接地(GND),再断开,模拟一个低电平脉冲。你应该能看到LED随之熄灭再亮起。这个测试可以验证“读->取反->写”这个核心功能是否正常。如果正常,再接回反馈线,就应该能振荡了。

5. 项目延伸与高级应用思考

完成了基础的环形振荡器,你已经掌握了其核心原理。但这个简单的项目可以作为一个跳板,探索更多数字电路和嵌入式系统的有趣概念。

5.1 从软件延迟到“真实”门延迟的探索

我们通过sleep函数模拟的延迟,是宏观的、软件可控的延迟。而真实数字集成电路(如74HC04非门)的传播延迟是物理特性,通常在纳秒级,且受温度、电压、负载影响。

进阶实验:测量“最小”振荡频率 尝试将代码中的延迟时间delay_ms设置为0,或者一个非常小的值(如1毫秒)。此时,循环的延迟主要由代码执行read_digital, 计算, write_digital 以及MicroPython解释器本身的开销决定。这个时间可以近似看作是你用软件模拟的这个“非门”的最小传播延迟 Td_min

  • 测量此时的输出频率 F_max。
  • 根据公式 Td_min ≈ 1/(2 * F_max)。
  • 你可以将这个Td_min与数据手册中一个典型74HC04门电路在特定条件下的传播延迟(如10ns)进行对比,直观感受软件模拟与硬件速度的巨大差异。这个实验能让你深刻理解为什么高速数字电路必须用专用硬件实现。

5.2 构建多级环形振荡器与占空比调整

软件模拟多级振荡器: 虽然如前所述,在单处理器上模拟多级串联门在效果上等同于增加单级延迟,但我们可以从代码结构上模拟,以加深理解。例如,模拟一个3级环形振荡器:

PYTHON
# 模拟三个串联的非门:A -> B -> C -> 反馈回A
state_a = 0 # 虚拟的“门A”输出,也是整个环路的最终输出
while True:
# 第一级门 (A)
input_b = state_a # B的输入是A的输出
# 模拟门A的延迟
sleep(delay_per_gate)
state_b = 1 - input_b # B的输出
 
# 第二级门 (B)
input_c = state_b
sleep(delay_per_gate)
state_c = 1 - input_c
 
# 第三级门 (C) - 输出反馈回A的输入
input_a = state_c
sleep(delay_per_gate)
state_a = 1 - input_a # 新的A输出,也是环路输出
 
# 将最终状态输出到物理引脚和LED
OUTPUT_PIN.write_digital(state_a)
display.set_pixel(2, 2, state_a * 9)

在这个结构中,总延迟 Td_total = 3 * delay_per_gate,振荡频率 F = 1/(2 * 3 * delay_per_gate)。你可以调整 delay_per_gate 来观察频率变化。

占空比调整: 标准的环形振荡器产生的是占空比50%的方波(高电平和低电平时间相等)。能否产生非50%的方波呢?可以,但需要修改“门”的行为。我们不再简单地执行“取反-延迟”,而是可以定义“输出高电平持续T_high,输出低电平持续T_low”。这实际上变成了一个可编程的脉冲发生器。

PYTHON
T_high = 300 # 高电平持续时间,毫秒
T_low = 700 # 低电平持续时间,毫秒
 
while True:
# 输出高电平阶段
OUTPUT_PIN.write_digital(1)
display.set_pixel(2, 2, 9)
sleep(T_high)
# 输出低电平阶段
OUTPUT_PIN.write_digital(0)
display.set_pixel(2, 2, 0)
sleep(T_low)

此时,周期 T = T_high + T_low,频率 F = 1/T,占空比 = T_high / T * 100%。这已经超越了经典环形振荡器的范畴,展示了用微控制器生成任意波形的能力。

5.3 在真实项目中的应用场景启示

虽然我们这个Microbit振荡器在精度和稳定性上无法与晶振媲美,但其揭示的原理和灵活性在某些场景下很有价值:

  1. 教学与原型验证:快速验证一个数字电路模块(如分频器、时序逻辑)是否需要时钟信号,以及大致频率范围是否合适。
  2. 可编程低频时钟源:为那些对时钟精度要求不高,但需要灵活改变频率的低速外设(如LED呼吸灯、蜂鸣器提示音、周期性传感器采样)提供时钟。你可以通过按钮、光敏电阻或其他传感器来动态调整delay_ms,从而实现频率调制。
  3. 理解时钟抖动与稳定性:通过这个项目,你可以实际测量软件生成时钟的周期抖动(每个周期时间的微小变化)。对比使用硬件定时器(如Microbit的Timer模块)生成的PWM信号,能深刻理解软件循环定时与硬件定时在精度上的本质区别。
  4. 故障注入与测试:在测试其他电路时,可以故意使用这个不太稳定的振荡器作为输入,来测试被测电路对时钟抖动的容忍度。

最后一点个人体会:玩转这个项目后,再看任何微控制器或CPU的数据手册,里面关于“内部RC振荡器”、“时钟树”、“PLL倍频”的描述,你会感到格外亲切。你会明白,所有复杂的时钟系统,其最原始的雏形,或许就是一个追求自我维持翻转的环形结构。从用一根导线短接两个引脚让代码“跑起来”,到理解现代处理器中GHz时钟的由来,这中间是一条充满奇思妙想的工程之路。而这个小小的Microbit环形振荡器,正是踏上这条路的一块有趣的垫脚石。