利用LPC802 USART模块生成精确50%占空比PWM信号

发布时间:2026/6/8 17:45:24

利用LPC802 USART模块生成精确50%占空比PWM信号 1. 项目概述与核心挑战在嵌入式硬件开发尤其是电机驱动、LED调光或者简单的数字信号合成领域生成一个稳定、精确的脉宽调制信号是家常便饭。大多数工程师的第一反应都是去用微控制器内置的通用定时器这确实是标准做法。但就像我最近在折腾NXP的LPC802这颗小巧的Cortex-M0芯片时发现的那样标准做法有时候会“卡壳”。具体来说当你需要从一个奇数分频的时钟源来产生PWM时LPC802的通用定时器模块就无法输出一个完美的50%占空比方波了。这个限制乍一看有点反直觉毕竟分频是硬件的基本功但深入其架构就会发现这与其计数器的工作模式和比较逻辑的对称性有关。当分频系数为奇数时计数器无法找到一个整数周期点使得高电平和低电平时间绝对相等从而导致占空比产生微小的偏差无法达到精确的50%。这个需求并非纸上谈兵。比如你的主时钟是9MHz但受限于外部传感器或驱动芯片的时序要求你需要一个1.8MHz的时钟信号计算一下9MHz / 5 1.8MHz这里的分频系数“5”就是个奇数。此时如果你固执地非要用通用定时器要么接受一个不是50%的占空比要么就得引入更复杂的软件补偿这无疑增加了系统的复杂性和不确定性。而LPC802另一个能输出时钟的模块——CLKOUT也面临着同样的奇数分频限制。这似乎把路给堵死了。但嵌入式开发的乐趣就在于总能在数据手册的角落里找到“柳暗花明又一村”的解决方案。这次救星是USART模块更准确地说是它的同步主模式时钟输出功能。这个通常被我们用来做串行通信的模块其内部有一个高精度的波特率发生器当配置为同步主模式时它可以将其内部时钟直接通过一个引脚输出而这个输出信号天生就是50%占空比的方波完全不受分频系数奇偶性的影响。下面我就来详细拆解如何利用LPC802的USART模块绕开定时器的限制生成一个稳定、精确、无需软件干预的50%占空比PWM信号。2. 方案选型与USART时钟输出原理为什么USART可以而通用定时器不行这需要从两者生成信号的根本机制上理解。通用定时器生成PWM通常依赖于一个自动重载的计数器和一个比较寄存器。计数器从0向上计数当计数值小于比较寄存器A的值时输出高电平大于等于时输出低电平直到计数到周期值后复位。要得到50%占空比比较寄存器A的值必须设定为周期值的一半。这里的关键在于“周期值”和“一半”都必须是整数。当输入时钟经过一个奇数N分频后作为定时器的时钟源时定时器每个计数周期的实际时钟周期是输入时钟的N倍。为了得到目标频率F_pwm定时器的自动重载值即周期需要设置为 (F_in / N) / F_pwm。如果N是奇数这个计算过程以及后续的“一半”计算很可能无法得到整数而比较寄存器只能存放整数值舍入误差就导致了占空比偏离50%。USART的同步时钟输出则走了另一条路。在同步主模式下USART需要为外部从设备提供一个位时钟。这个时钟U_SCLK由USART的波特率发生器直接产生其公式为U_SCLK Fclk / (BRGVAL 1)。其中Fclk是USART模块的输入时钟BRGVAL是波特率发生器寄存器BRG中设置的一个整数值。这个电路本质上是一个分频器它对输入时钟Fclk进行BRGVAL1分频。重要的是这个分频器输出的波形是对称的方波。无论BRGVAL1是奇数还是偶数其输出都是50%占空比。这是因为其内部电路设计保证了输出在半个分频周期为高另外半个周期为低。因此我们只需要将USART配置为同步主模式并使其持续输出这个时钟那么该时钟引脚上的信号就是一个完美的、占空比50%的PWM波。频率则由Fclk和BRGVAL共同决定。注意这里存在一个关键概念区分。USART模块内部有两个“时钟”一个是用于其内部逻辑如移位寄存器的“模块时钟”另一个是输出到引脚给外部设备使用的“位时钟”U_SCLK。我们这里要利用的正是后者。在配置时我们需要确保USART的“模块时钟”通路正确但最终输出到引脚并作为PWM使用的是“位时钟”信号。基于这个原理我们的方案流程图就非常清晰了配置系统时钟确定主时钟频率并确保USART模块能获得正确的时钟源。引脚功能映射将USART的时钟输出功能通过开关矩阵分配到某个具体的GPIO引脚上。计算并设置频率根据所需的PWM频率和主时钟频率计算BRGVAL值并配置USART波特率发生器。模式与输出控制将USART配置为同步主模式并启用连续时钟输出功能。启动时钟输出通过选择USART的时钟源激活时钟输出电路。这个方案的另一个巨大优势是“一旦启动无需干预”。在配置为连续输出模式后只要系统不掉电、不复位这个PWM信号就会一直稳定输出完全不消耗CPU资源实现了真正的硬件级PWM生成。3. 硬件平台与开发环境搭建3.1 核心硬件LPC802微控制器与开发板这次实验的核心是NXP LPC802微控制器。这是一颗基于Arm Cortex-M0内核的入门级MCU最高运行频率15MHz具备16KB Flash和2KB SRAM。其外设包括I2C、USART、SPI、一个多速率定时器、一个通用32位定时器、12位ADC等性价比很高。我使用的是官方的LPCXpresso802开发板型号OM40000 Rev A。这块板子将LPC802的所有引脚通过排针引出并集成了板载调试器通过一根Micro-USB线即可完成供电、编程和调试非常方便。我们的目标信号将从PIO0_8引脚输出。选择这个引脚的原因在于根据LPC802的用户手册USART0的时钟输出信号可以灵活地映射到多个引脚上PIO0_8是其中之一并且在LPCXpresso802开发板上这个引脚正好位于Arduino兼容接口上方便我们用示波器探头进行测量。你需要准备以下硬件LPCXpresso802开发板一块。一台安装有KEIL MDK或类似IDE的个人电脑。一根Micro-USB数据线用于连接开发板和电脑。一台示波器用于观测生成的PWM波形。两根杜邦线用于连接开发板引脚和示波器探头。3.2 软件环境与SDK配置软件开发环境我选择的是KEIL MDK v5.27编译器用的是Arm Compiler 6。当然你也可以使用IAR或者MCUXpresso IDE原理相通。更重要的是NXP为LPC802提供的软件开发套件。我们需要从NXP官网下载LPCXpresso802 SDK版本我用的是v2.4.0。这个SDK包含了所有外设的驱动库、示例代码和板级支持包能极大简化我们的开发工作。SDK的目录结构通常很清晰。我们需要关注的是boards\lpcxpresso802\demo_apps\这个路径我们的工程将创建在这里。为了验证本文的方案你可以直接创建一个新的MDK工程或者基于某个USART示例工程进行修改。关键是要正确包含SDK中的驱动源文件drivers目录和头文件路径。在开始写代码之前请务必确认你的工程已经包含了fsl_usart.c/.h,fsl_swm.c/.h,fsl_power.c/.h以及fsl_clock.c/.h这些关键驱动文件。3.3 硬件连接与测量点确认硬件连接非常简单用Micro-USB线将开发板的CN1接口连接到电脑。找到开发板上的Arduino接口CN3。根据开发板原理图找到标有PIO0_8的排针。这通常对应Arduino接口的某个数字引脚。将示波器探头的信号线钩子连接到PIO0_8引脚探头的接地线连接到开发板上的任何一个GND引脚。实操心得在连接示波器探头时尽量使用探头自带的接地弹簧而不是长长的接地夹线这样可以减少测量回路避免引入噪声让观察到的波形更干净。特别是当PWM频率达到MHz级别时这个细节很重要。4. 分步实现从时钟源到PWM输出下面我将结合代码片段详细讲解每一个配置步骤。我们的目标是生成一个1.8MHz、50%占空比的PWM信号假设系统主时钟为9MHz。4.1 第一步配置系统主时钟为9MHzLPC802的系统时钟可以来自内部的FRO。FRO可以输出9MHz, 12MHz, 15MHz等频率。我们需要将其设置为9MHz并作为系统主时钟。#include fsl_power.h #include fsl_clock.h void BOARD_BootClockFRO18M(void) { // 关闭看门狗时钟可选但推荐在初始化早期进行 CLOCK_DisableClock(kCLOCK_Wdt); // 设置FRO为系统时钟源并配置其输出频率。 // 注意函数名中的18M指的是FRO的振荡器频率经过分频后得到9MHz的系统时钟。 POWER_DisablePD(kPDRUNCFG_PD_FRO_OUT); CLOCK_SetupFROClocking(12000000U); // 此函数内部会调用ROM API配置FRO CLOCK_SetFLASHAccessCyclesForFreq(9000000U); // 设置Flash访问等待周期以适应9MHz CLOCK_AttachClk(kFRO_to_MAIN_CLK); // 将FRO连接到主时钟 // ... 其他外设时钟的默认配置 }在main函数最开始调用BOARD_BootClockFRO18M()即可。这个函数是SDK提供的它封装了底层ROM API的调用将FRO配置为18MHz振荡然后通过内部分频给系统提供9MHz的主时钟。这一步确保了整个芯片包括后续的USART模块有一个稳定的9MHz时钟基础。4.2 第二步将USART0时钟输出映射到PIO0_8引脚LPC802的引脚功能非常灵活通过开关矩阵模块可以将大部分数字外设功能映射到几乎任何I/O引脚。我们需要把USART0的时钟输出功能kSWM_USART0_SCLK分配给PIO0_8。#include fsl_swm.h #include fsl_iocon.h void BOARD_InitPins(void) { // 初始化SWM开关矩阵和IOCONI/O配置模块的时钟 CLOCK_EnableClock(kCLOCK_Swm); CLOCK_EnableClock(kCLOCK_Iocon); // 将USART0的SCLK时钟输出功能分配到PIO0_8引脚 SWM_SetMovablePinSelect(SWM0, kSWM_USART0_SCLK, kSWM_PortPin_P0_8); // 配置PIO0_8的I/O属性禁用上下拉电阻、标准模式、非反转 IOCON-PIO[0][8] ((IOCON-PIO[0][8] (~(IOCON_PIO_FUNC_MASK | IOCON_PIO_MODE_MASK | IOCON_PIO_SLEW_MASK | IOCON_PIO_INVERT_MASK | IOCON_PIO_DIGIMODE_MASK | IOCON_PIO_OD_MASK))) | IOCON_PIO_FUNC(1) // 功能选择为特殊功能由SWM决定 | IOCON_PIO_MODE(IOCON_MODE_INACT) // 无上下拉 | IOCON_PIO_SLEW(IOCON_SLEW_STANDARD) // 标准转换速率 | IOCON_PIO_INVERT(IOCON_INVERT_DISABLE) // 不反转 | IOCON_PIO_DIGIMODE(IOCON_DIGITAL_EN) // 数字模式 | IOCON_PIO_OD(IOCON_OD_DISABLE)); // 禁用开漏输出 // 配置完成后可以禁用SWM时钟以省电可选 CLOCK_DisableClock(kCLOCK_Swm); }这里有几个关键点SWM_SetMovablePinSelect是SDK提供的函数第一个参数是SWM实例LPC802只有SWM0第二个参数是要分配的外设信号第三个参数是目标引脚。IOCON配置决定了引脚的电气特性。对于时钟输出我们通常配置为数字输出、无上下拉、标准驱动能力。IOCON_PIO_FUNC(1)表示该引脚的功能由SWM模块决定。4.3 第三步计算并配置USART0输出频率这是核心步骤。我们需要根据公式U0_SCLK Fclk / (BRGVAL 1)来计算BRGVAL。目标频率U0_SCLK: 1.8 MHzUSART模块输入时钟Fclk: 在我们的配置中USART的时钟源默认选择的是main_clk也就是9MHz。计算BRGVAL:BRGVAL (Fclk / U0_SCLK) - 1 (9MHz / 1.8MHz) - 1 5 - 1 4。因此我们需要将波特率发生器寄存器BRG的值设置为4。在SDK中这通常在初始化USART时完成。#include fsl_usart.h void USART0_Configuration(void) { usart_config_t usartConfig; USART_GetDefaultConfig(usartConfig); // 获取默认配置结构体 // 配置为同步模式、主模式 usartConfig.syncMode kUSART_SyncMode; // 同步模式 usartConfig.slaveMode kUSART_SlaveModeDisabled; // 主模式 // 配置波特率本质是配置时钟输出频率 // 注意此处的baudRate_Bps参数在同步模式下实际配置的是BRG值对应的时钟频率 // SDK的USART_Init函数内部会根据传入的baudRate_Bps和模块时钟频率自动计算BRG值。 // 但为了精确控制我们更推荐直接设置BRG值。 usartConfig.baudRate_Bps 1800000U; // 传入目标频率SDK会计算 // 初始化USART0 USART_Init(USART0, usartConfig, 9000000U /* 传入模块时钟频率: 9MHz */); // 更直接的方法直接操作寄存器设置BRG值 // USART0-BRG 4; // 直接写入BRG寄存器 }注意事项SDK的USART_Init函数会根据你传入的baudRate_Bps和srcClock_Hz自动计算并设置BRG寄存器。在大多数情况下这很方便。但如果你需要非常精确地控制或者想确认设置的值可以直接读写USART0-BRG寄存器。对于1.8MHz输出和9MHz输入时钟BRG寄存器值必须为4。4.4 第四步配置USART0为同步主模式并启用连续输出仅仅设置频率还不够必须明确告诉USART模块“请进入同步主模式并且持续输出时钟不要等待数据发送。”// 在USART_Init之后进行额外的模式配置 // 1. 明确设置为同步主模式虽然配置结构体已设置但再次确认寄存器 USART0-CFG | (USART_CFG_SYNCEN_MASK | USART_CFG_SLAVEEN_MASK); // 使能同步模式禁用从模式即主模式 // 2. 启用连续时钟输出 USART0-CTL | USART_CTL_CC_MASK; // 设置CC (Continuous Clock) 位为1USART_CFG_SYNCEN_MASK: 该位置1使能同步模式。USART_CFG_SLAVEEN_MASK: 该位置0表示主模式置1表示从模式。我们这里需要主模式。USART_CTL_CC_MASK: 这是实现“连续输出无需软件干预”的关键。该位置1后只要USART的时钟源被激活时钟就会持续从SCLK引脚输出而不需要你通过写入数据寄存器来触发发送。如果没有这个设置USART只会在发送数据时才会产生时钟脉冲。4.5 第五步启动USART0时钟输出所有配置都完成后最后一步是给USART0模块“上电”并选择时钟源。LPC802中USART的时钟源选择是一个独立的寄存器UART0CLKSEL。// 3. 选择USART0的时钟源从而启动模块和时钟输出 // 首先确保USART0模块本身被使能上电 CLOCK_EnableClock(kCLOCK_Usart0); // 然后选择时钟源。这里选择frg0clk它默认就是由main_clk驱动的。 // 查阅参考手册frg0clk对应的选择值是0x2。 SYSCTL0-UART0CLKSEL 0x2; // 选择frg0clk作为USART0的时钟源 // 注意复位后UART0CLKSEL的默认值是0x7表示“无时钟”因此模块不工作。 // 将其设置为一个有效的时钟源值如0x1, 0x2等模块即开始工作时钟开始输出。SYSCTL0-UART0CLKSEL这个寄存器是关键开关。在复位状态下它是0x7无时钟USART模块处于“停滞”状态。当我们将其设置为一个有效的时钟源编号例如0x2对应frg0clk时钟信号立刻会流入USART模块并根据之前的配置从PIO0_8引脚输出1.8MHz的方波。如果你想停止PWM输出只需要将这个寄存器再次写回0x7即可。4.6 完整代码整合与主函数将以上所有步骤整合到一个工程的主函数中代码如下#include fsl_device_registers.h #include fsl_power.h #include fsl_clock.h #include fsl_swm.h #include fsl_iocon.h #include fsl_usart.h int main(void) { // 1. 芯片级初始化解除复位配置时钟 POWER_DisablePD(kPDRUNCFG_PD_IRC_OUT); // 使能内部IRC如果需要 POWER_DisablePD(kPDRUNCFG_PD_IRC); // 使能内部IRC BOARD_BootClockFRO18M(); // 配置系统主时钟为9MHz // 2. 初始化引脚将USART0_SCLK映射到PIO0_8 BOARD_InitPins(); // 3. 配置USART0同步主模式、1.8MHz频率、连续时钟输出 USART0_Configuration(); // 这个函数包含了4.3和4.4步的所有配置 // 4. 启动USART0时钟输出 CLOCK_EnableClock(kCLOCK_Usart0); SYSCTL0-UART0CLKSEL 0x2; // 选择frg0clk // 5. 主循环此时PWM已经在硬件上持续输出CPU可以休眠或执行其他任务 while (1) { // 这里可以添加其他应用代码 // 例如让芯片进入低功耗模式以省电 // __WFI(); // 等待中断 } }将代码编译、下载到LPCXpresso802开发板后用示波器测量PIO0_8引脚你应该能看到一个稳定的1.8MHz、50%占空比的方波信号。5. 调试技巧、常见问题与扩展应用5.1 示波器测量与验证将示波器探头连接到PIO0_8后你可能需要调整示波器的设置来获得稳定的波形显示触发设置设置为边沿触发选择上升沿触发电平设置在1.5V左右对于3.3V系统。时基由于是1.8MHz信号周期约555ns。可以将时基调到200ns/div或500ns/div这样屏幕上能显示几个完整的周期。幅值垂直刻度调到1V/div或2V/div确保波形在屏幕中央。测量功能使用示波器的自动测量功能读取频率和占空比。理论上频率应非常接近1.8MHz占空比应为50.0%。由于时钟源和分频都是数字逻辑实际测量结果会极其精确。如果看不到信号请按以下步骤排查检查供电和编程确认开发板的电源灯亮并且程序已成功下载。检查引脚连接确认示波器探头确实接触到了PIO0_8引脚和GND最好用万用表测一下PIO0_8对地是否有3.3V左右电压说明引脚已激活。检查代码配置顺序确保SYSCTL0-UART0CLKSEL 0x2;是最后一步。如果先启动了时钟源但USART的同步模式或连续输出模式还未配置可能会输出不正确的信号。检查时钟源确认BOARD_BootClockFRO18M()函数被正确调用系统主时钟确实是9MHz。可以在代码中翻转一个GPIO并用示波器测量其频率来间接验证。5.2 常见问题与解决方案问题现象可能原因排查与解决方案无信号输出1. 引脚映射错误。2. USART时钟源未启动。3. USART模块未使能。1. 检查SWM_SetMovablePinSelect函数参数确认映射到了正确的引脚如P0_8。2. 检查SYSCTL0-UART0CLKSEL寄存器值是否为0x7以外的有效值如0x2。3. 确认CLOCK_EnableClock(kCLOCK_Usart0);已被调用。输出频率不正确1. 系统主时钟频率不对。2. BRG值计算或设置错误。3. USART模块时钟源选错。1. 验证系统时钟配置确保FRO输出和分频设置正确。2. 重新计算BRG值BRGVAL (Fclk / Desired_Freq) - 1。检查USART0-BRG寄存器的实际值。3. 确认frg0clk的源是main_clk且未经过其他分频。占空比不是50%1. 示波器测量误差或触发问题。2.配置模式错误最可能。1. 尝试调整示波器触发电平和时基使用高带宽探头。2.务必确认USART_CTL_CC位已被置1连续时钟模式并且USART配置为同步主模式SYNCEN1且SLAVEEN0。在非连续模式下时钟只在发送时产生。波形有毛刺或抖动1. 电源噪声。2. 探头接地不良。3. 引脚负载过重。1. 确保开发板供电稳定远离噪声源。2. 使用探头的接地弹簧缩短接地回路。3. PWM输出引脚不要直接驱动大容性负载必要时串联一个小电阻如22-100欧姆。5.3 方案扩展与灵活性这个方案的优势不仅在于解决奇数分频问题更在于其灵活性和可扩展性。动态频率调整虽然我们演示的是固定频率输出但你完全可以在运行时动态修改USART0-BRG寄存器的值来改变PWM频率。只要在修改前确保时钟输出已停止UART0CLKSEL0x7修改BRG值后再重新使能时钟源即可输出新的频率。这比重新配置定时器要简单直接。多路PWM输出LPC802有两个USART模块。你可以用同样的方法配置USART1从另一个引脚输出第二路独立频率的50%占空比PWM。这对于需要多个不同时钟基准的应用非常有用。非50%占空比本方案核心是生成50%占空比。如果你需要其他占空比这个方案本身不直接支持。但你可以将其作为一个精准的50%基准时钟再用一个简单的GPIO翻转通过另一个定时器中断控制来对其进行门控从而产生其他占空比的信号不过这需要CPU干预。更低频率输出公式U_SCLK Fclk / (BRGVAL 1)中BRGVAL是一个16位寄存器最大值65535。这意味着在9MHz主时钟下最低可以输出约137Hz的PWM信号。对于很多低频应用也足够了。通过这次对LPC802 USART模块的“非典型”应用我们跳出了定时器的思维定式利用通信外设的时钟生成单元优雅地解决了一个特定的PWM生成难题。这种深入理解外设底层原理并灵活运用的能力正是嵌入式工程师从“会用”到“精通”的关键一步。在实际项目中当标准路径走不通时不妨多翻翻数据手册也许某个不起眼的功能寄存器就是打开新世界大门的钥匙。

相关新闻