任何合成器的关键元件都是其振荡器电路。模拟合成器通常具有两个或更多个可独立控制的振荡器。但是,任何踏入过模拟压控振荡器 (VCO)原理图互联网兔子洞的人都知道,它们通常很复杂且杂乱。一些杂乱来自可以优雅处理的功能,例如多个控制电压输入。但是,即使简单的振荡器电路在原理图中被隔离,剩下的部分仍然很复杂,因为热反馈技巧可以使振荡器在其组件升温时保持调谐。
当我考虑创建自己的合成器时,VCO 电路的复杂性和混乱性一直是我的绊脚石,尽管我喜欢模拟合成,但我还是无法摆脱其混乱的电子缺陷。当我对Roland Juno 系列合成器产生好感时,一切都改变了。
Roland 的 Juno-6于 1982 年上市,是当时其他复音合成器更实惠的替代品。它也是款使用 DCO 代替传统 VCO 的合成器。与其他合成器相比,这大大提高了乐器的调谐稳定性,因为 DCO 使用数字电路来控制振荡器模拟信号的频率。考虑到这一点,使用 DCO 代替 VCO 当然是有代价的。许多人喜欢两个略微失谐的 VCO 齐奏的“温暖”声音,而这种声音很难用基于 DCO 的合成器模仿。但是,可以将调制效果应用于 DCO 的干输出信号,以产生华丽、优美的声音。
总体而言,Arduino Uno 和 Nano 开发板的普及性和低成本,再加上这些数字平台预装了 16 MHz晶体振荡器的事实,使得创建廉价的 DCO 变得非常容易。
使用微控制器定时器模块创建 DCO
查看任何现代微控制器的数据表,您都会在其外设中找到一个定时器模块。定时器模块允许嵌入式设计人员在独立于 CPU (中央处理单元)的嵌入式系统后台设置运行计数器。此外,定时器模块可以在多种情况下中断 CPU,例如当它们溢出计数寄存器或达到特定计数时。嵌入式设计人员可以配置中断条件以满足其特定应用的需求。
对于这个项目, ATMega328P的定时器模块(Arduino Nano 和 Uno 的大脑)充当合成器的 DCO。通过配置定时器模块的时钟源和计数值,可以在音频频率触发定时器模块中断。DCO 输出是通过在这些周期性中断的中断服务例程 (ISR) 期间操纵微控制器的 GPIO 引脚来实现的。
在接下来的章节中,我将讨论该项目背后的硬件和软件设计,然后展示一些来自该自制合成器的音频片段。
使用 Arduino Nano 的音频合成器硬件
在介绍该项目涉及的不同硬件以及它们如何协同工作之前,表 1 显示了 BOM(物料清单)。
数量成分
1
Arduino Nano v3.x
4470 nF电容
12.1 毫米筒形插孔
11/4”单声道开关插孔
24.7k电阻
347k电阻
7100k电阻
1100k电阻
2100k电位器
11 M 电位器
23SPDT 微型瞬时开关
1开关DC-DC 转换器(5 V 输出)
1Microchip MCP23017 I/O 扩展器
1LF411 运算放大器(运放)
接下来,让我们看一下图 2 的系统图,它展示了该合成器的各个部分是如何组合在一起的。
合成器的硬件系统图。
图 2. 合成器的硬件系统图。
键盘部分由 23 个 SPDT 开关组成,每个键一个。其中 16 个键路由到 MCP23017 I/O 扩展器,其余 7 个键直接路由到 Arduino Nano 上的 GPIO 输入。然后 MCP23017 通过I2C连接到 Arduino Nano 。
从这里开始,Arduino Nano 处理来自键盘的输入,并根据这些按键在 D11、D12 和 D10 上生成三个独立的振荡器输出。Arduino Nano 上这些数字引脚的输出被路由到求和放大器电路,该电路的原理图如图 3 所示。
求和放大器电路的示意图。
图 3。 求和放大器电路原理图。
求和放大器包含三个电位器。它们分别控制振荡器 2 和 3 的音量以及乐器的主音量。放大器电路的输出直接路由到 ?” 单声道音频插孔,因此可轻松直接插入吉他放大器。
为了给该乐器供电,我使用标准的 2.1 毫米 9 V DC 吉他踏板筒形插孔。此外,该插孔的 +9 V 被路由到小型 DC-DC 开关电源转换器以产生 5 V 电源连接。+5 V 电源为 MCP23017 供电。Arduino 通过其 Vin 引脚由 +9 V 电源供电。运算放大器的 V+ 和 V- 由筒形插孔的 +9 V 和接地连接提供,5 V 电源用作运算放大器的浮动接地连接。
图 4 显示了该系统各部分的连接方式。
整个系统示意图
图 4. 整个系统示意图
创建音频合成器的软件方面
该项目软件的主要任务是解释键盘按钮的输入并相应地操作定时器模块寄存器(请参阅此处的 Arduino 草图 PDF 以获取代码)。在草图中的 setup() 函数之前,声明了几个全局变量,包括两个大型的 2D 数组,其中包含与音符相对应的定时器模块寄存器值。setup() 函数的过程遵循图 5 的流程图,其中仅涉及:
设置 GPIO 输入和输出
开始 I2C 通信
初始化三个定时器模块
启用中断
选择定时器 B 模块的时钟源作为定时器 A 的时钟
启用全局中断
该仪器的 Arduino 草图的 setup() 函数的流程图。
图 5. 该仪器的 Arduino 草图的 setup() 函数的流程图。
图 6 显示了表示 Arduino 草图的 loop() 函数的流程图。
此仪器的 Arduino 草图的 loop() 函数流程图
图 6.该仪器的 Arduino 草图的 loop() 函数流程图.
循环函数执行三个主要任务:
检测键盘上按下的键
根据特定键的音调设置定时器模块参数
设置门变量以允许振荡器信号转发到各自的 GPIO 引脚
循环函数评估连接到键盘的每个 I/O 端口,直到检测到按下的按钮。端口的评估顺序是从键盘上的音符到音符,这意味着低音符实际上具有更高的优先级。虽然我考虑使用 GPIO 和 I2C 中断来处理键盘按钮按下,但我终使用了连续轮询方法,并且我没有注意到因此而产生的任何不利性能。
,图 7 表示 ATMega328P 中三个定时器模块各自的中断服务程序。
Arduino Nano 的 ATMega328P 定时器模块的中断服务程序流程图。
图 7.Arduino Nano 的 ATMega328P 定时器模块的中断服务程序流程图。
如果设置了门控变量,则每个 ISR 都会切换其输出引脚值。此切换会为每个振荡器生成音频频率输出。