i.MX6ULL PWM控制蜂鸣器 —— 从原理到响起来本文配套代码基于i.MX6ULL裸机开发已在正点原子/野火开发板验证前一篇GPT | i.MX6ULL 芯片 | 基础学习一句话总结GPT翻转GPIO你手动按开关累但灵活PWM模块自动开关设定好就不用管了CPU去干别的一、为什么要用PWM控制蜂鸣器上一篇我们学了GPT定时器知道定时器可以精确地产生时间间隔。但你有没有想过定时器只能产生方波——高电平、低电平来回切。方波能干嘛让LED闪烁 ✅让蜂鸣器嘟嘟响 ✅但让蜂鸣器唱出不同音调方波做不到 ❌因为音调 频率。哆262Hz来294Hz咪330Hz……要让蜂鸣器发出不同频率的声音你需要能调节输出频率的能力。这就是PWM的意义。PWMPulse Width Modulation脉冲宽度调制可以调频率→ 控制音调高低调占空比→ 控制声音大小一句话GPT是开关PWM是可调节的开关。二、PWM到底是什么别被术语吓到PWM本质还是方波但它给你两个旋钮可以调|←—— 周期 T ———→| |___ ___ ___ | | | | | |___| |___| |___ |←-→| 高电平时间 t 占空比 t / T × 100%旋钮1频率周期T频率 1/T频率越高蜂鸣器振动越快音调越高哆(Do) 262Hz就是每秒振动262次旋钮2占空比高电平时间t / 周期T占空比50% 一半时间高、一半时间低 → 声音最大占空比10% 很少时间高 → 声音小占空比0% 一直是低电平 → 不响类比频率 你敲鼓的速度快高音慢低音占空比 你敲鼓的力度重大声轻小声三、蜂鸣器分两种你用的是哪种在写代码之前必须先搞清楚你板子上的蜂鸣器类型不然代码写对了也听不到响。3.1 有源蜂鸣器 vs 无源蜂鸣器特性有源蜂鸣器无源蜂鸣器内部有没有振荡源✅ 有❌ 没有给直流电就响✅ 给电就响❌ 不响必须给方波能调音调吗❌ 不能固定频率✅ 能你给什么频率它发什么音能调音量吗❌ 只能调占空比改大小✅ 能价格贵一点便宜外观区别底部封闭底部可以看到绿色电路板怎么判断你的是哪种最简单的方法给它通3.3V直流电。响了 → 有源蜂鸣器没响 → 无源蜂鸣器关键区别有源蜂鸣器你只需要控制GPIO高低电平就能嘟嘟响但不能变调。相当于你给它电源它自己会叫。无源蜂鸣器你必须给它方波信号PWM它才能响但可以唱出不同音调。相当于你不仅要给它电源还要告诉它用什么频率振动。⚠️大多数开发板用的是有源蜂鸣器只能嘟——不能唱歌。如果你的板子是有源的下面的PWM调音调部分你可以跳过只看占空比控制音量的部分。四、i.MX6ULL的PWM模块长什么样i.MX6ULL有4个PWM通道PWM1~PWM4每个通道都可以独立配置。4.1 PWM的核心参数一个PWM信号由三个参数决定周期 1 / 频率 频率 时钟源 / ( prescaler × (period 1) ) 占空比 高电平时间 / 周期用大白话说时钟源PWM模块的心跳i.MX6ULL的PWM时钟通常是66MHzIPG时钟或更高预分频器prescaler把时钟分频让它变慢。比如66MHz ÷ 64 1.03MHz周期寄存器period决定一个完整方波有多长占空比寄存器duty决定高电平占多少4.2 计算公式不用背理解就行PWM频率 时钟源 / (预分频 × (PWMPR 1))举个例子时钟源 66MHz预分频 64PWMPR 49寄存器值频率 66000000 / (64 × 50) 20625Hz ≈ 20KHz这个频率人耳听不到超声波了所以如果要让蜂鸣器响你需要调低频率到20Hz~20KHz之间。想让蜂鸣器发出262Hz哆假设预分频64PWMPR 66000000 / (64 × 262) - 1 ≈ 3940算出来就行填到寄存器里蜂鸣器就唱哆了。五、硬件连接蜂鸣器怎么接到i.MX6ULL上5.1 典型电路图i.MX6ULL GPIO/PWM引脚 │ ▼ ┌────┴────┐ │ 三极管 │ ← 为什么需要三极管看下面解释 │ (S8050) │ └────┬────┘ │ ┌────┴────┐ │ 蜂鸣器 │ └────┬────┘ │ GND5.2 为什么不能直接接GPIO因为GPIO引脚输出电流太小了i.MX6ULL的GPIO最大输出电流大约8mA而有源蜂鸣器工作电流通常需要20~30mA。直接接要么声音特别小要么烧坏GPIO。三极管的作用 开关放大器GPIO输出小电流 → 控制三极管的开关三极管接通 → 外部电源3.3V/5V的大电流流过蜂鸣器蜂鸣器响了类比GPIO是遥控器三极管是大门蜂鸣器是屋子里的人。遥控器按按钮小电流大门打开三极管导通屋子里的人蜂鸣器开始干活。5.3 注意PWM引脚选择i.MX6ULL的PWM引脚是复用的同一个引脚可以有多种功能引脚默认功能PWM功能备注GPIO1_IO01GPIOPWM1常用GPIO1_IO03GPIOPWM2常用GPIO1_IO05GPIOPWM3部分板子引出GPIO1_IO07GPIOPWM4部分板子引出具体哪个引脚接蜂鸣器看你开发板的原理图。大多数开发板已经把蜂鸣器接好了你只需要配置对应的PWM通道就行。六、代码实现让蜂鸣器响起来6.1 整体思路第一步配置引脚复用 → 告诉芯片这个引脚我要当PWM用 第二步配置PWM参数 → 告诉PWM模块用什么频率、多大占空比 第三步启动PWM → 蜂鸣器开始响 第四步调参数 → 变音调、变音量6.2 寄存器配置裸机C代码/* * i.MX6ULL PWM控制蜂鸣器 —— 裸机版 * 目标让无源蜂鸣器发出262Hz哆的声音 */ /* 1. 引脚复用配置 */ /* 假设蜂鸣器接在PWM1对应的引脚上 */ /* 需要配置IOMUXC寄存器把引脚功能切换为PWM */ /* 2. PWM寄存器基地址 */ #define PWM1_BASE 0x02080000 #define PWM_CR (PWM1_BASE 0x00) /* 控制寄存器 */ #define PWM_SR (PWM1_BASE 0x04) /* 状态寄存器 */ #define PWM_IR (PWM1_BASE 0x08) /* 中断寄存器 */ #define PWM_SAR (PWM1_BASE 0x0C) /* 采样寄存器占空比*/ #define PWM_PR (PWM1_BASE 0x10) /* 周期寄存器 */ #define PWM_CNTR (PWM1_BASE 0x14) /* 计数器 */ /* 3. 配置PWM参数 */ void pwm_beep_init(void) { /* 时钟源 IPG 66MHz */ /* 预分频 64 → PWM时钟 66MHz/64 1.03MHz */ /* 目标频率 262Hz哆 */ /* 周期 1.03MHz / 262 - 1 ≈ 3929 */ unsigned int period 3929; /* 周期值 */ unsigned int duty period / 2; /* 占空比50% → 声音最大 */ /* 写周期寄存器 */ *(volatile unsigned int *)PWM_PR period; /* 写采样寄存器占空比*/ *(volatile unsigned int *)PWM_SAR duty; /* 配置控制寄存器 * - 使能PWM * - 选择时钟源IPG * - 设置预分频值 * - FIFO模式 0不用FIFO */ *(volatile unsigned int *)PWM_CR (1 0) /* EN 1使能 */ | (1 16) /* CLKSRC 01IPG时钟 */ | (63 4); /* PRESCALER 63实际分频64*/ } /* 4. 改变音调 */ void pwm_set_frequency(unsigned int freq_hz) { unsigned int pwm_clock 66000000 / 64; /* 1.03MHz */ unsigned int period pwm_clock / freq_hz - 1; *(volatile unsigned int *)PWM_PR period; *(volatile unsigned int *)PWM_SAR period / 2; /* 保持50%占空比 */ } /* 5. 改变音量调占空比*/ void pwm_set_duty(unsigned int duty_percent) { unsigned int period *(volatile unsigned int *)PWM_PR; unsigned int duty period * duty_percent / 100; *(volatile unsigned int *)PWM_SAR duty; } /* 6. 停止蜂鸣器 */ void pwm_beep_stop(void) { /* 占空比设为0 → 不输出方波 → 蜂鸣器不响 */ *(volatile unsigned int *)PWM_SAR 0; }6.3 代码里每一行在干什么关键解释为什么用(volatile unsigned int *)volatile告诉编译器这个地址的值可能会被硬件改变每次都要重新读不要优化掉操作硬件寄存器的标准写法少了会出bug为什么预分频写63而不是64硬件设计如此寄存器值 实际分频值 - 1写63 → 实际分频64倍。和GPT定时器一样的套路。为什么占空比设为period/2period/2 50%占空比 → 高电平和低电平时间一样长 → 声音最大最清晰改成period/4 25% → 声音变小改成0 0% → 不响七、让蜂鸣器唱歌音符频率对照表如果你用的是无源蜂鸣器你可以让它唱出不同的音符。7.1 中音区频率表音符简谱频率(Hz)PWM周期值预分频64哆1(Do)2623929来2(Re)2943502咪3(Mi)3303120发4(Fa)3492950索5(Sol)3922626拉6(La)4402339西7(Ti)4942084哆(高)1̇(Do)52319657.2 播放《小星星》的代码/* 小星星1 1 5 5 6 6 5 - 4 4 3 3 2 2 1 */ struct note { int freq; /* 频率 */ int duration; /* 持续时间(毫秒) */ }; struct note song[] { {262, 400}, {262, 400}, {392, 400}, {392, 400}, {440, 400}, {440, 400}, {392, 800}, /* 一闪一闪亮晶晶 */ {349, 400}, {349, 400}, {330, 400}, {330, 400}, {294, 400}, {294, 400}, {262, 800}, /* 满天都是小星星 */ {0, 0} /* 结束标记 */ }; void play_song(void) { int i 0; while (song[i].freq ! 0) { if (song[i].freq 0) { pwm_set_frequency(song[i].freq); } delay_ms(song[i].duration); /* 你自己实现的延时函数 */ pwm_beep_stop(); delay_ms(50); /* 音符之间的间隔避免粘连 */ i; } }八、常见问题 踩坑记录Q1蜂鸣器不响排查顺序确认蜂鸣器类型有源蜂鸣器接PWM没用它只需要直流电。用万用表测一下。确认引脚复用配置了没引脚默认是GPIO你得切换到PWM功能。确认三极管电路对不对BCE引脚别接反S8050的引脚顺序查数据手册。确认时钟源使能了没CCMClock Controller Module里要把PWM模块的时钟打开。确认占空比不是0占空比0 一直低电平 不响。Q2蜂鸣器声音很小占空比太小 → 调到50%驱动电流不够 → 检查三极管电路是不是用的NPN管S8050/S9014蜂鸣器坏了 → 换一个试试Q3音调不准检查时钟源频率是不是66MHz用示波器或逻辑分析仪测检查预分频和周期寄存器的计算有没有错频率 时钟源 / (预分频 × (周期1))别漏了那个 1Q4有源蜂鸣器能用PWM吗可以但只能调音量占空比不能调音调有源蜂鸣器内部有自己的振荡器外部频率它不搭理如果你只想要嘟嘟声用GPIO翻转 延时就够了不需要PWM九、PWM vs GPT定时器翻转GPIO有什么区别对比项GPT翻转GPIOPWM模块能产生方波✅✅能精确调频率⚠️ 靠中断有抖动✅ 硬件级精确能自动输出❌ 需要中断服务函数不断翻转✅ 配置好就自动输出CPU不用管占用CPU较高频繁中断几乎不占能同时控制多个通道❌ 一个定时器只管一个✅ 4个PWM通道独立适合场景简单闪烁、实验蜂鸣器唱歌、电机调速、LED调光一句话总结GPT翻转GPIO 你手动按开关累但灵活PWM模块 自动开关设定好就不用管了CPU去干别的