利用Microchip PRG外设实现硬件级三角波生成与VCO控制
1. 项目缘起一个被低估的“瑞士军刀”外设最近在做一个需要精确控制频率的模拟信号发生器项目核心需求是生成一个频率可线性调节的三角波作为电压控制振荡器VCO的核心。一开始的思路很常规用DAC输出一个模拟电压去控制一个模拟VCO芯片或者用MCU的定时器配合DMA和查表法在DAC上生成波形。前者成本高、电路复杂后者则严重消耗CPU和内存资源在高频或高分辨率下力不从心。就在我纠结于方案选型时重新审视了手头这颗Microchip的PIC单片机数据手册发现了PRGPattern Generator这个外设。说实话之前一直把它当作一个简单的PWM或波形发生模块但深入研究发现用它来构建一个数字域到模拟域的“柔性桥梁”实现从三角波到VCO的功能简直是量身定定做。这个项目就是一次将PRG外设潜力挖掘到极致的实战记录整个过程充满了“原来还能这样用”的惊喜。PRG直译过来是“模式发生器”但它的能力远不止生成固定模式。它本质上是一个高度可配置、带有时钟预分频、可编程计数器和比较逻辑的数字信号处理单元。它能够基于内部或外部时钟按照预设的规则如计数方向、比较匹配实时生成复杂的数字波形序列并且其输出可以直接驱动IO或者通过事件系统触发其他外设如DAC、ADC、定时器。我们这次要做的就是利用它生成一个高线性度、频率可实时数字控制的三角波再通过一个简单的RC滤波电路将其平滑为模拟电压最终用这个电压去控制一个压控振荡器VCO的核心频率从而实现一个全数字可调的VCO系统。这个方案的优势在于它几乎不占用CPU资源所有波形生成和频率调整都由硬件自动完成响应速度快精度高且成本极低。2. PRG外设深度解构不止于PWM在动手之前必须彻底吃透PRG的工作原理。如果只把它当PWM用那就大材小用了。PRG的核心是一个可上下计数的计数器以及一组与之关联的比较寄存器。它的工作模式非常灵活我主要用到了其中两种关键模式来构建三角波。2.1 核心工作模式单斜坡与三角波模式PRG最常见的模式是“单斜坡”模式。在这种模式下计数器从0开始向上计数达到一个设定的周期值PRGxPR寄存器后复位归零重新开始。同时它有一个比较寄存器PRGxRyy当计数器值等于比较值时可以产生一个中断或者触发一个事件。这其实就是PWM的基本原理周期固定通过改变比较值来改变占空比。但我们要生成三角波需要的是“三角波”模式。在此模式下计数器的行为发生了变化它从0开始向上计数达到周期值后不是复位而是改变计数方向开始向下计数直到减到0再改变方向向上计数如此循环往复。这样计数器的值本身就是一个数字三角波这个数字三角波的频率由时钟源和周期值共同决定而它的幅度峰值就是周期值。F_triangular F_clock / (4 * PRGxPR)。这里除以4是因为一个完整的三角波周期包含上坡和下坡每个坡的计数值跨度都是PRGxPR。注意不是所有型号的Microchip单片机PRG都支持三角波模式在选型时务必查阅具体型号的数据手册确认PRG支持的模式列表。例如PIC18-Q43系列中的部分型号就明确支持。2.2 关键输出与事件系统连接世界的桥梁生成了数字三角波计数器值只是第一步。如何把它变成我们需要的信号这里就需要用到PRG的输出单元和事件系统。输出单元PRG可以将内部计数器的值或者比较匹配的结果映射到特定的物理IO引脚上。例如我们可以配置让PRG在计数器向上计数时输出高电平向下计数时输出低电平这样在引脚上就能直接得到一个方波其占空比固定为50%但频率与三角波相同。但这还不是我们最终要的。事件系统这才是PRG的“灵魂”所在。PRG可以作为一个“事件发生器”。我们可以配置当计数器达到峰值周期值或谷值0时产生一个“事件”。这个事件不是中断不消耗CPU而是一个硬件级别的触发信号可以在芯片内部直接传递给其他外设比如DAC想象一下这个场景PRG工作在三角波模式计数器值实时变化。我们配置DAC让它使用“外部触发”模式。然后将PRG计数器达到峰值或谷值或者每次计数变化时产生的事件连接到DAC的触发输入端。这样每当PRG计数器更新DAC就会立即被触发将当前的计数器值通过数据总线转换为对应的模拟电压输出。于是一个完全由硬件实现的、实时更新的数字三角波到模拟三角波的转换通道就建立了CPU完全不需要干预。这正是本项目方案的精髓。3. 从数字三角波到模拟VCO控制电压的完整链路理解了核心机制我们来搭建完整的信号链。目标通过改变一个控制字比如一个变量线性地改变最终VCO的输出频率。3.1 链路第一步PRG生成可变频率的数字三角波我们希望三角波的频率F_tri受一个控制量K控制。根据公式F_tri F_clock / (4 * PRGxPR)要改变F_tri最直接的方法是动态改变PRGxPR寄存器的值。K值越大PRGxPR越小F_tri越高。但是直接修改PRGxPR可能会在波形周期中间产生跳变导致输出波形出现毛刺或相位不连续。为了解决这个问题PRG外设通常提供了“缓冲寄存器”或“双缓冲”机制。我们可以在一个缓冲寄存器如PRGxPRB中写入新的周期值然后通过一个特定事件如当前周期结束或软件命令安全地将缓冲寄存器的值同步到活跃的PRGxPR寄存器中。这样就能实现频率的无缝切换。操作步骤如下初始化PRG设置时钟源例如选择内部高频振荡器分频后的时钟F_clock 64 MHz并启用三角波模式。计算初始PRGxPR。假设我们需要一个1 kHz的基频三角波PRGxPR F_clock / (4 * F_tri) 64,000,000 / (4 * 1000) 16000。配置PRG的事件输出。我们需要将“计数器值变化”或“周期结束”事件产生出来用于触发DAC。具体配置哪个事件取决于DAC支持的触发方式。有些DAC支持每个时钟周期更新这就需要PRG每个计数步进都产生事件有些则支持在三角波的峰值/谷值更新这样可以降低DAC的更新速率节省功耗。这里我们假设DAC支持后者配置PRG在计数器达到峰值PRGxPR值和谷值0时产生事件。3.2 链路第二步DAC将数字三角波转换为模拟电压接下来需要将PRG计数器的实时值送给DAC。这里不能简单地用CPU去读取PRG计数器值再写入DAC那会引入不确定的延迟和CPU开销。必须使用硬件联动。配置DAC将DAC设置为“外部事件触发”模式。其参考电压Vref选择稳定的内部参考或外部基准这决定了输出模拟电压的范围。例如选择Vref 3.3V。建立事件连接使用微控制器的交叉事件矩阵如果支持将PRG产生的事件输出连接到DAC的触发输入通道。这是一个纯硬件配置通常在初始化时通过寄存器设置完成。数据供给DAC被触发时它需要知道要转换什么数字值。这里有两种方式直接数据寄存器如果PRG和DAC集成度足够高可能支持直接数据通路。但更通用的方式是使用DMA。使用DMA这是最优雅、CPU零开销的方案。配置一个DMA通道其触发源就是PRG产生的那个事件。DMA的源地址设置为PRG计数器的值寄存器这是一个只读寄存器实时反映当前计数值目的地址设置为DAC的数据寄存器。这样每当PRG事件触发DMA硬件会自动将当前的计数器值“搬运”到DACDAC随即开始转换。整个过程完全由硬件完成。电压计算PRG计数器范围是0 ~ PRGxPR。DAC假设是12位输入范围是0 ~ 4095。我们需要将PRG的计数器值映射到DAC的输入范围。如果PRGxPR恰好是4095那就一一对应。如果不是可能需要通过DMA进行缩放计算或者更简单地在软件中设置PRGxPR为4095然后通过改变F_clock的分频来调节频率。但为了频率控制更灵活通常我们会保持F_clock固定动态改变PRGxPR并在DMA传输过程中加入一个简单的缩放运算如果DMA支持数据加工功能或者使用一个查找表。为了简化本实例假设PRGxPR最大值设置为4095这样DAC输出Vout (PRG_Counter / 4095) * Vref。输出的就是一个0-3.3V的模拟三角波。3.3 链路第三步RC滤波与VCO接口DAC输出的是阶梯状的三角波因为数字值在离散变化。虽然DAC内部有采样保持但为了获得更平滑的模拟波形需要在DAC输出端加一个简单的RC低通滤波器截止频率设置为略高于所需三角波的最高频率成分通常是基频的5-10倍以上。例如三角波最高频率为10kHz滤波器截止频率可以设在50-100kHz。一个简单的单极点RC滤波器一个电阻串联一个电容到地就足够了。f_c 1 / (2πRC)。选择R1kΩ为了得到约100kHz的截止频率计算C 1 / (2π * 1000 * 100,000) ≈ 1.6 nF可以选择一个1.5nF或2.2nF的电容。滤波后的平滑三角波电压就可以直接送入电压控制振荡器VCO的压控输入端。VCO的频率F_vco与控制电压V_ctrl成线性关系理想情况下F_vco K_vco * V_ctrl F_offset。其中K_vco是VCO的增益单位Hz/VF_offset是零输入电压时的频率。至此整个闭环形成软件修改控制变量K→ 改变PRG周期值PRGxPR→ 改变数字三角波频率F_tri→ 改变DAC输出更新速率但注意DAC输出的电压幅度范围是固定的0-Vref变化的只是电压变化的频率→ 经过滤波后得到一个频率为F_tri的模拟三角波电压 → 此电压驱动VCO使VCO的输出频率F_vco跟随F_tri变化。我们实际上用三角波的频率调制了VCO的频率实现了一个频率调制FM功能。如果我们的目的是让VCO的频率直接正比于控制字K那么我们需要让DAC输出的电压幅度而不是频率随K变化。这就引出了下一个关键点。4. 核心实现将频率控制转换为幅度控制上面的链路产生了一个频率变化的三角波但VCO通常需要的是一个直流或慢变化的控制电压来设定其中心频率。我们需要的是一个电压幅度可编程的三角波而不是频率可变的。这就需要调整思路。新方案PRG仍然生成一个固定频率的、高线性度的三角波作为“载波”。但这次我们不改变它的频率而是利用另一个DAC通道或者同一个DAC分时复用来生成一个可编程的直流电压作为“调制信号”。然后通过一个模拟乘法器或使用MCU内部的可编程增益放大器PGA如果支持将三角波与直流电压相乘。乘法结果就是一个幅度随直流电压变化的三角波其频率恒定。具体硬件实现调整PRG配置为固定频率三角波模式例如10 kHz。其计数器值通过DMA1实时发送给DAC1输出一个0-3.3V、10kHz的固定三角波V_tri(t)。软件根据控制字K计算出一个对应的目标幅度系数A0到1之间。通过另一个DAC2或主DAC的不同通道输出一个直流电压V_dc A * Vref。使用一个模拟乘法器芯片如AD633将V_tri(t)和V_dc相乘得到V_out(t) V_tri(t) * V_dc / (缩放因子)。这样V_out(t)就是一个幅度为A * Vref的三角波频率仍为10kHz。将V_out(t)经过RC滤波滤除乘法器可能引入的高频噪声后送入VCO。这个方案更接近传统的VCO控制通过改变一个直流电压这里由V_dc体现来线性改变VCO的频率。而三角波的存在可以用来对VCO进行频率调制FM如果V_dc本身也是一个音频信号那就实现了模拟FM合成。纯数字简化方案如果微控制器内部集成了运算放大器和模拟开关或许可以通过PGA来实现数字控制的幅度缩放从而省去外部乘法器。但更通用和灵活的还是使用外部模拟乘法器。5. 代码实操与寄存器配置要点理论清晰后来看具体的代码实现片段以MPLAB® X IDE和Harmony 3框架为例但原理通用。我们以实现固定频率三角波、通过DAC输出为例。5.1 PRG初始化关键代码// 假设使用PRG1 void PRG1_Initialize(void) { // 1. 停止PRG PRG1CONbits.ON 0; // 2. 配置时钟源选择FOSC/2 作为时钟源假设FOSC 64MHz则PRG时钟为32MHz PRG1CLKbits.CLKSEL 0x1; // 选择特定的时钟源具体值查手册 PRG1CLKbits.CLKDIV 0; // 分频系数 // 3. 设置周期寄存器决定三角波峰值和频率 // 目标三角波频率 10kHz, F_clock 32MHz // PRG1PR F_clock / (4 * F_tri) 32,000,000 / (4 * 10,000) 800 PRG1PR 800; // 4. 设置运行模式三角波模式自动输出使能 PRG1CONbits.MODE 0x2; // 三角波模式具体值查手册 PRG1CONbits.OPS 0x1; // 输出模式选择可能对应引脚输出波形 // 5. 配置事件输出我们希望在每个三角波峰值和谷值即计数器方向改变时产生事件 PRG1EVTbits.STROBE 0x3; // 设置事件触发条件例如上下坡触发 // 6. 启用PRG PRG1CONbits.ON 1; }5.2 DAC与DMA联动配置void DAC1_Initialize(void) { // 1. 配置DAC参考电压为内部3.3V DAC1CONbits.VREFSEL 1; // 2. 配置DAC输出使能 DAC1CONbits.OE 1; // 3. 配置DAC为外部事件触发模式 DAC1CONbits.TRIGSEL 0x5; // 选择触发源例如对应PRG1的事件 DAC1CONbits.TRIGEN 1; // 使能触发模式 } void DMA0_Initialize(void) { // 1. 配置DMA通道0 DMA0REQbits.IRQSEL 0xXX; // 选择触发源为 PRG1 事件具体值查手册 DMA0CONbits.MODE 0; // 每次触发传输一个字 DMA0CONbits.SIZE 1; // 传输数据大小为字16位 // 2. 设置源地址PRG1计数器的值寄存器地址 DMA0STBAL (uint16_t)PRG1CNT; DMA0STBAH 0; // 假设8位单片机高位为0 // 3. 设置目的地址DAC1数据寄存器地址 DMA0DSTAL (uint16_t)DAC1DAT; DMA0DSTAH 0; // 4. 使能DMA通道 DMA0CONbits.ON 1; }5.3 动态改变三角波幅度通过改变DAC参考或后续缩放如果需要动态改变幅度在我们的简化模型里就是改变送入乘法器的直流电压V_dc。这通过更新另一个DACDAC2的值即可。void set_VCO_ControlVoltage(float target_voltage) { // target_voltage 范围 0 - 3.3V uint16_t dac_code; // 计算DAC代码假设DAC为12位Vref3.3V dac_code (uint16_t)((target_voltage / 3.3) * 4095); // 更新DAC2的数据寄存器如果是缓冲模式注意同步时机 DAC2DAT dac_code; }6. 调试心得与常见问题排查在实际调试中遇到问题不要慌按照信号流逐级排查。问题1PRG没有输出波形。检查时钟确认PRG的时钟源是否使能分频配置是否正确。用示波器测量PRG时钟输入引脚如果有时钟输出功能或使用调试器查看PRG计数器寄存器是否在变化。检查模式与使能确认PRGxCONbits.ON1且模式MODE设置为三角波模式。检查输出引脚配置是否正确是否被其他功能复用。检查周期值确保PRGxPR寄存器被正确写入一个非零值。如果值为0或过小频率会极高可能超出观察范围。问题2DAC没有输出或者输出是固定值。检查触发链路这是最容易出问题的一环。首先确认PRG的事件是否产生。可以配置PRG事件触发一个IO引脚翻转用示波器看是否有脉冲。然后确认DAC的触发源选择寄存器TRIGSEL是否对应了PRG的事件编号。最后确认DAC的触发模式TRIGEN是否使能。检查DMA如果使用DMA检查DMA的触发源选择是否正确通道是否使能。可以在DMA传输完成中断里加一个IO翻转来测试DMA是否被触发。检查数据供给在非DMA模式下检查CPU是否在事件中断中正确读取了PRG计数器值并写入DAC。在DMA模式下检查源地址和目的地址是否正确。检查参考电压测量DAC的Vref引脚电压是否正常。问题3输出三角波线性度差有台阶或毛刺。台阶问题这是数字采样的本质。DAC输出的本来就是阶梯波。确保后续RC滤波器的截止频率设置合理能够平滑掉采样台阶。如果台阶非常明显可能是DAC的更新速率即PRG事件频率相对于三角波频率太低。提高PRG的时钟频率或降低三角波频率可以增加每个三角波周期内的采样点数使台阶更细密。毛刺问题可能是电源噪声、地线干扰或数字信号对模拟部分的串扰。确保模拟部分DAC、滤波器、VCO的电源经过良好的LC滤波地线布局合理数字地和模拟地单点连接。DAC输出端可以串联一个小的电阻如50-100Ω再接入滤波器有助于减少反射和毛刺。问题4改变控制字K时VCO频率变化不线性或有延迟。非线性检查VCO本身的线性度。用精密可调电压源直接输入VCO测量频率-电压曲线。如果VCO非线性可能需要软件进行查表补偿。延迟如果延迟发生在控制字K到电压V_dc的转换那主要是DAC的建立时间和滤波器的响应时间。选择建立时间快的DAC并优化滤波器带宽在平滑度和响应速度间取舍。如果延迟发生在频率切换改变PRG周期时确保使用了双缓冲机制在三角波周期边界进行同步更新避免中间截断波形。7. 方案评估与拓展思考这个基于Microchip PRG外设的VCO控制方案其优势在于极高的效率和确定性。整个波形生成和控制电压产生的闭环都在硬件中自动完成CPU仅在需要改变设定值时例如响应按键或通信指令才介入大大解放了CPU资源去处理其他任务。同时硬件时序非常精确没有软件延迟带来的抖动特别适合对实时性要求高的应用比如音乐合成、软件定义无线电SDR中的本振生成等。当然它也有局限性。首先它依赖于MCU是否具备PRG、DAC、DMA和灵活的事件系统对芯片型号有要求。其次最终输出波形的精度和性能受限于DAC的分辨率、建立时间以及外部模拟电路滤波器、乘法器、VCO的性能。拓展方向多通道与复杂调制如果MCU有多个PRG和DAC可以生成多个相位同步或异步的三角波实现更复杂的调制波形。闭环控制加入ADC采样VCO的实际输出频率与目标频率在CPU中做比较形成数字锁相环DPLL或数字频率控制DFC环路实现自动频率校准克服VCO的温漂和非线性。波形存储与播放将PRG的计数器输出通过DMA存入缓冲区同时用另一个定时器触发DAC播放可以实现任意波形的生成。此时PRG可以作为一个高精度的定时触发源。与运放结合利用片内运放配合DAC和PRG事件可以构建硬件实现的积分器、微分器生成更复杂的模拟信号处理链路。回过头看PRG这个外设就像一颗隐藏的宝石它把波形生成、定时、事件触发这些常用功能用高度可配置的硬件模块固化下来。在资源有限且对实时性要求高的嵌入式场景中善于利用这类外设进行“硬件协处理”往往是提升系统性能和可靠性的关键。这次从三角波到VCO的设计过程就是一次很好的硬件资源整合实践其思路完全可以迁移到其他需要精密时序和信号生成的场合。