1. 项目概述当ESP32遇见复古电波几年前我在旧货市场淘到一台七十年代的晶体管收音机拨动那个硕大的调谐旋钮时指针在泛黄的频率刻度盘上平滑移动伴随着“沙沙”的底噪和突然响起的清晰人声那种充满仪式感的寻台过程是如今数字电台一键直达无法替代的。正是这份情怀驱使我动手复刻这种体验但内核要换上新世纪的“心脏”。于是就有了这个项目用一块集成ESP32的圆形智能屏CrowPanel 2.1英寸作为“脸面”用经典的TEA5767 FM模块作为“耳朵”再配上一个旋转编码器充当“手指”打造一台外观复古、内核现代的FM收音机。这个项目的核心价值远不止于怀旧。它是一次非常典型的嵌入式系统开发实践完美串联了微控制器ESP32的GPIO控制、I2C总线通信、以及人机交互HMI设计。你不仅是在做一台收音机更是在深入理解如何让一个强大的32位MCU去“驯服”一个简单的单功能芯片并通过一个直观的界面将这个过程优雅地呈现出来。最终成品它没有自动搜台、没有RDS信息显示、甚至没有频率数字显示它唯一且全部的功能就是让你通过旋转编码器像几十年前一样在一条绘制精美的模拟刻度盘上手动寻找那个隐藏在空中的电波信号。这种纯粹的、专注的交互本身就是一种乐趣。2. 核心硬件选型与设计思路拆解2.1 主控与显示单元为何是CrowPanel ESP32圆形屏市面上ESP32开发板选择众多为何独选这款CrowPanel 2.1英寸圆形屏模块这源于项目的一个核心矛盾复古的圆形外观与现代的显示需求。传统的方案可能需要ESP32 DevKit V1板子外加一个SPI/I2C的圆形OLED或TFT屏这意味着需要额外的连接、供电和结构固定复杂度陡增。CrowPanel模块提供了一个优雅的一体化解决方案。它将ESP32-WROOM-32E模组、480x480分辨率的圆形LCD显示屏、一个旋转编码器、一个TF卡槽以及必要的电源管理电路全部集成在了一个直径约6厘米的圆形PCB上。这带来了几个决定性优势极简的硬件连接我们只需要通过模块背面引出的I2C接口标注清晰连接收音机模块再接上功放和喇叭即可。主控、显示、输入设备编码器的互连问题在出厂时就已经解决了。强大的本地渲染能力ESP32本身性能足够驱动这块屏进行复杂的图形绘制。我们可以用LVGL、TFT_eSPI等库绘制出极具质感的复古表盘包括渐变色、阴影、精细的刻度线和字体这是许多低端显示屏难以实现的。即时的交互反馈旋转编码器与显示屏同轴一体转动时的手感和视觉反馈同步且直接这是实现“模拟调谐”体验的关键物理基础。注意选择此类一体化模块时务必确认其引出的接口引脚定义。CrowPanel的I2C引脚通常是固定的如GPIO 21-SDA GPIO 22-SCL且其3.3V电源输出带载能力需要评估以决定是否能为TEA5767和PAM8403供电。2.2 收音核心TEA5767 FM模块的再审视TEA5767是一个有年头的单芯片FM收音机IC了但它至今仍在DIY领域焕发活力原因在于其极简和易用。它通过I2C接口完全受控内部集成了从射频输入到音频输出的完整FM接收链路包括低噪声放大器、混频器、中频滤波器、鉴频器以及立体声解码器。为什么不是更先进的RDA5807或SI4703后两者功能更强支持RDS、软静音等。但本项目追求的是“复古”与“纯粹”。TEA5767的操控方式非常原始和直接写入频率数据读出状态信息。它没有花哨的功能这正好契合了我们“手动调谐刻度盘”的核心创意。它的这种“傻”让我们能将所有开发精力聚焦于人机交互与视觉呈现上而不是去处理复杂的数字电台信息。关键参数考量供电电压典型3.3V与ESP32完美兼容。接收频率范围通常为76-108MHz覆盖日本频段和欧美频段通过配置寄存器可以设置。音频输出直接输出解调后的立体声音频信号需要接入功放。天线模块上的FM天线接口通常需要接一段约75cm的导线作为简易天线。接收灵敏度很大程度上取决于这天线的长度、摆放位置和周围环境。2.3 音频与结构如何让声音“对味”声音部分我们选择了PAM8403这款经典的D类音频功放模块。它价格低廉、效率高、驱动能力强最大2x3W且自带音量电位器。这里有一个细节复古收音机的喇叭通常功率不大且放在木制或塑料腔体里声音带有一种特有的“箱体感”。为了模仿这种感觉我没有选用高性能的宽频扬声器而是特意找了一个0.25W、8Ω的老式小喇叭。这种喇叭频响窄中频突出恰恰模拟了老收音机那种不算Hi-Fi但温暖的声音特质。结构设计的巧思项目的描述中提到“The housing acts like a sound box”。这意味着整个外壳我使用了一个3D打印的圆形腔体被设计成了共鸣箱。显示屏模块安装在前面板喇叭紧贴在后壳内侧声音通过环绕屏幕的一圈开孔辐射出来。这种设计不仅外观上复刻了老式收音机的扬声器格栅在声学上也利用腔体增强了低频响应让小喇叭听起来不那么单薄。3. 系统连接与I2C通信深度解析3.1 硬件接线图与电源管理整个系统的接线极其简洁下图清晰地展示了各模块间的连接关系------------------- ------------------- ------------------- | CrowPanel ESP32 | | TEA5767 FM | | PAM8403 | | 圆形屏模块 | | 收音模块 | | 功放模块 | ------------------- ------------------- ------------------- | | | | | | | 3.3V ------------------| VCC | | | | GND ------------------| GND | | | | GPIO21 (SDA) ----|------| SDA | | | | GPIO22 (SCL) ----|------| SCL | | | | | | | | | | | | L_OUT ------------------| L_IN | | | | R_OUT ------------------| R_IN | | | | | | | | | | ANT ~~~~~[75cm导线] | | ------------------- ------------------- | VCC GND ---[电源输入] | SP / SP- ---[Speaker] -------------------电源方案详解 CrowPanel模块通过USB-C口供电其板载的3.3V LDO理论上能提供约500mA电流。TEA5767工作电流很小约20mAPAM8403在驱动小功率喇叭时静态电流约10mA中等音量下总电流可能在100-200mA左右。因此从CrowPanel的3.3V引脚为两个模块供电在大多数情况下是可行的。但为了系统最稳定特别是如果你希望音量开得很大我推荐以下两种进阶方案独立供电使用一个外部的5V USB电源同时给CrowPanel通过USB口和PAM8403通过其电源端子供电。PAM8403模块内部有稳压5V输入没问题。TEA5767则仍从CrowPanel取3.3V电。这样可以避免功放大动态时对MCU和收音模块造成电源噪声干扰。使用大容量电容至少在CrowPanel的3.3V输出端并联一个100-470uF的电解电容可以平滑电源纹波对改善收音模块的抗干扰能力有奇效。3.2 TEA5767的I2C驱动原理与寄存器配置与TEA5767的所有交互都通过读写其内部的5个字节寄存器完成。理解这些寄存器是编写驱动代码的关键。通信基础I2C地址TEA5767的固定地址是0x60写操作。请注意这是7位地址在Wire库中通常直接使用这个值。数据格式每次向TEA5767写入5个字节从它读取5个字节。核心寄存器解析写入 当我们想要设置频率时需要构造一个5字节的数据包发送给TEA5767。字节位 7位 6位 5位 4位 3位 2位 1位 0说明1MUTESM00PLL[13]PLL[12]PLL[11]PLL[10]静音搜索模式PLL高4位2PLL[9]PLL[8]PLL[7]PLL[6]PLL[5]PLL[4]PLL[3]PLL[2]PLL低8位311111111默认值0xFF411111111默认值0xFF511111111默认值0xFF最关键的是PLL值的计算。TEA5767采用频率合成器PLL值决定了本振频率。公式为 [ PLL (int)(4 * (Frequency * 1000000 225000) / 32768) ] 其中Frequency是以MHz为单位的目标频率如98.5。225000是中频频率225kHz。计算出的PLL值是一个14位的整数需要拆分到寄存器1和2的高位和低位。例如设置频率为98.5MHz计算PLL:4 * (98.5e6 225000) / 32768 ≈ 12035将12035转换为二进制0010 1110 0000 0011(14位)拆分高4位0010- 0x2放入字节1的低4位低8位1110 0000 0011- 0xE0 0x03等等这里需要仔细处理。12035的二进制是10 1110 0000 0011。我们需要14位所以是0010 1110 0000 0011。字节1的低4位取PLL[13:10]即0010(二进制2)。字节2的8位取PLL[9:2]即11 1000 0000不对PLL[9:2]是从第9位到第2位。让我们重新按位标号从13到0 PLL[13]0, PLL[12]0, PLL[11]1, PLL[10]0, PLL[9]1, PLL[8]1, PLL[7]1, PLL[6]0, PLL[5]0, PLL[4]0, PLL[3]0, PLL[2]0, PLL[1]0, PLL[0]1? 计算有误。我们写个简单程序验证或心算12035。 更稳妥的方法是使用位操作pll_byte1_high4bits (pll_value 10) 0x0F; // 获取高4位pll_byte2_low8bits (pll_value 2) 0xFF; // 获取[9:2]位还有一个PLL[1:0]两位它们被忽略或者说是PLL的小数部分TEA5767内部处理。所以对于12035pll_byte1_high4bits (12035 10) 0x0F (12035/1024)取整 11 0x0F 11 (0x0B)pll_byte2_low8bits (12035 2) 0xFF (3008) 0xFF 3008 % 256 3008 - 11*256 3008 - 2816 192 (0xC0)所以字节1 (MUTE7) | (SM6) | 0x0B字节2 0xC0。其他重要控制位MUTE位71静音0正常播放。在切换频率时可以先静音设置好后再取消静音能避免刺耳的噪声。SM位6搜索模式。置1后芯片会从当前频率开始向高频搜索下一个信号合格的电台找到后自动停止并更新频率。本项目未使用此功能因为我们追求手动调谐。从TEA5767读取状态 读取的5个字节包含了频率信息、信号强度、立体声标志等。其中字节1和字节2包含了当前锁定的PLL值我们可以反向计算出当前频率用于在屏幕上更新指针位置虽然本项目是开环控制但读取验证是个好习惯。字节4的位7RF是“准备好标志”位6-位0是ADC输出的信号强度电平可以用来在屏幕上绘制一个简单的信号强度条增加可玩性。4. 软件实现从编码器到复古表盘4.1 开发环境搭建与库依赖本项目基于Arduino框架开发因其对ESP32和各类库的支持非常成熟。安装ESP32开发板支持在Arduino IDE的“开发板管理器”中搜索并安装“ESP32 by Espressif Systems”。安装显示驱动库CrowPanel屏幕的驱动芯片通常是ST7789或类似。你需要根据屏幕型号安装对应的库例如TFT_eSPI。安装后需要修改库中的用户配置文件User_Setup.h正确设置引脚定义、屏幕分辨率和驱动芯片型号。CrowPanel的供应商通常会提供示例配置。安装编码器库为了稳定处理旋转编码器特别是消抖推荐使用ESP32Encoder库或AiEsp32RotaryEncoder库。后者对ESP32优化更好内置了消抖和加速功能。安装图形库可选但推荐如果你想绘制复杂的、带平滑动画的表盘强烈建议使用LVGL库。它是一个轻量级、功能强大的嵌入式图形库虽然学习曲线稍陡但能实现专业级的UI效果。如果追求简单用TFT_eSPI自带的绘图函数也完全足够。4.2 旋转编码器输入处理与频率控制逻辑旋转编码器是本项目交互的灵魂。我们需要将其转动动作转化为频率的增减。// 示例使用AiEsp32RotaryEncoder库 #include AiEsp32RotaryEncoder.h #define ROTARY_ENCODER_A_PIN 33 // CrowPanel上编码器A相引脚 #define ROTARY_ENCODER_B_PIN 32 // B相引脚 #define ROTARY_ENCODER_BUTTON_PIN 0 // 编码器按键引脚如有 #define ROTARY_ENCODER_VCC_PIN -1 // 如果编码器需要上电接此引脚 AiEsp32RotaryEncoder rotaryEncoder AiEsp32RotaryEncoder(ROTARY_ENCODER_A_PIN, ROTARY_ENCODER_B_PIN, ROTARY_ENCODER_BUTTON_PIN, ROTARY_ENCODER_VCC_PIN); void setup() { rotaryEncoder.begin(); rotaryEncoder.setup([] { rotaryEncoder.readEncoder_ISR(); }); // 设置中断服务例程 rotaryEncoder.setBoundaries(875, 1080, false); // 设置边界87.5MHz - 108.0MHz以0.1MHz为单位 rotaryEncoder.setAcceleration(100); // 设置加速转动越快步进值越大 } void loop() { static float currentFreq 98.5; // 初始频率 if (rotaryEncoder.encoderChanged()) { long newValue rotaryEncoder.readEncoder(); float newFreq newValue / 10.0; // 将编码器值转换为频率MHz if (abs(newFreq - currentFreq) 0.1) { // 频率变化步长0.1MHz currentFreq newFreq; setFreqToTEA5767(currentFreq); // 调用函数设置TEA5767频率 updateDisplayNeedle(currentFreq); // 更新屏幕指针位置 } } // ... 其他循环任务 }关键逻辑解析边界设置setBoundaries(875, 1080)并非直接设置频率而是设置编码器的计数范围。这里我们将87.5MHz映射为875108.0MHz映射为1080这样编码器每变化1代表0.1MHz100kHz的频率变化符合FM广播的频道间隔习惯。加速功能setAcceleration(100)使得快速转动编码器时步进值会增大从而可以快速跨越无台区慢速转动时则精细调谐。这完美模拟了老式收音机调谐旋钮的惯性手感。消抖处理优秀的编码器库已在内部通过硬件中断和软件滤波实现了消抖这是稳定性的保证自己用digitalRead实现很容易出现跳变。4.3 复古表盘UI的绘制技巧绘制一个逼真的复古表盘是项目的视觉核心。以下是用TFT_eSPI库绘制的基本思路#include TFT_eSPI.h TFT_eSPI tft TFT_eSPI(); void drawDial() { int centerX tft.width() / 2; int centerY tft.height() / 2; int radius 100; // 1. 清屏并绘制背景色复古绿 tft.fillScreen(TFT_DARKGREEN); // 2. 绘制外圆环和刻度盘 tft.drawCircle(centerX, centerY, radius, TFT_WHITE); tft.drawCircle(centerX, centerY, radius-5, TFT_GOLD); // 3. 绘制主要刻度如88, 92, 96, 100, 104, 108 MHz for (int freq 88; freq 108; freq 4) { float angle map(freq, 88, 108, 210, 510) * PI / 180.0; // 映射到210-510度右侧270度弧段 int x1 centerX (radius - 15) * cos(angle); int y1 centerY (radius - 15) * sin(angle); int x2 centerX radius * cos(angle); int y2 centerY radius * sin(angle); tft.drawLine(x1, y1, x2, y2, TFT_WHITE); // 绘制频率文字 tft.setTextColor(TFT_GOLD); tft.drawNumber(freq, x1 - 10, y1 - 10, 2); // 使用合适字体 } // 4. 绘制次要刻度每1MHz for (float freq 88.0; freq 108.0; freq 1.0) { float angle map(freq*10, 880, 1080, 210, 510) * PI / 180.0; // 注意映射范围 int x1 centerX (radius - 8) * cos(angle); int y1 centerY (radius - 8) * sin(angle); int x2 centerX radius * cos(angle); int y2 centerY radius * sin(angle); tft.drawLine(x1, y1, x2, y2, TFT_WHITE); } // 5. 绘制一个静态的红色指针初始位置 drawNeedle(98.5); } void drawNeedle(float freq) { // 先擦除旧的指针可以通过重绘局部背景或存储上一帧实现 eraseOldNeedle(); int centerX tft.width() / 2; int centerY tft.height() / 2; int needleLength 80; // 将频率映射到角度例如88MHz - 210度 108MHz - 510度 float angle map(freq * 10, 880, 1080, 210, 510) * PI / 180.0; int xTip centerX needleLength * cos(angle); int yTip centerY needleLength * sin(angle); // 绘制指针线红色较粗 tft.drawLine(centerX, centerY, xTip, yTip, TFT_RED); // 在指针根部画一个小圆点 tft.fillCircle(centerX, centerY, 5, TFT_RED); }提升质感的技巧抗锯齿TFT_eSPI库支持一些基本图形的抗锯齿绘制如drawSmoothCircle能显著改善圆形和斜线的锯齿感。渐变背景可以使用循环绘制多个不同颜色、半径递减的实心圆来模拟简单的径向渐变营造深邃感。字体寻找或设计一款复古的数码或衬线字体使用setFreeFont()加载能让刻度数字更有味道。阴影效果在绘制白色刻度线和文字时先用深色如深灰色在偏移一个像素的位置绘制一次再绘制主色能产生简单的立体阴影效果。动画平滑更新指针时不要直接跳到新位置。可以计算新旧角度差分多步如5步逐步绘制每步之间短暂延时形成平滑的动画。这需要更精细的帧管理和局部刷新技术。5. 组装、调试与优化实录5.1 硬件组装步骤与注意事项焊接与连接首先将杜邦线焊接到TEA5767和PAM8403模块的引脚上。建议使用不同颜色的线区分电源、地和信号。连接CrowPanel时务必对照其引脚定义图确认I2C引脚SDA, SCL的位置。天线处理TEA5767模块上的天线焊盘焊接一段长约75cm的导线。这不是最优方案但最简单。更好的做法是焊接一个耳机插座这样可以使用标准的耳机线作为天线效果更好且美观。将天线尽量拉直远离电源线和MCU能有效提升接收灵敏度。腔体与喇叭安装如果使用3D打印外壳确保喇叭与出声孔之间贴合紧密必要时可以在喇叭边缘贴一圈海绵胶条防止声音短路即声音从喇叭背面直接传到正面削弱低频。将CrowPanel用螺丝或卡扣固定在前壳喇叭用热熔胶固定在后壳内侧。电源接入可以先通过CrowPanel的USB口供电进行测试。如果使用外部5V电源给功放注意共地问题确保所有模块的GND连接在一起。实操心得电源噪声抑制在第一次通电测试时你可能会从喇叭里听到明显的“滋滋”高频噪声这很可能是来自ESP32或开关电源的噪声通过电源线串入了功放或收音模块。解决方法磁珠/电感在CrowPanel的3.3V输出到TEA5767的VCC之间串联一个100Ω的电阻或一个磁珠如600Ω100MHz并并联一个10uF的电解电容和一个0.1uF的陶瓷电容到地构成一个简单的π型滤波电路。独立供电如前所述给功放模块单独供电是最彻底的解决方案。软件静音在ESP32进行Wi-Fi扫描、屏幕大量刷新等可能产生较大电源干扰的操作前通过I2C发送静音指令给TEA5767操作完成后再取消静音。5.2 软件调试与常见问题排查即使硬件连接正确软件也可能遇到各种问题。下面是一个常见问题排查表现象可能原因排查步骤与解决方案屏幕无显示1. 电源未接通或接触不良。2. 背光未开启。3. 库配置错误引脚、驱动芯片。1. 检查USB线、电源电压。2. 在代码中尝试tft.init()后调用tft.setRotation()和tft.fillScreen(TFT_RED)测试基本功能。3. 仔细核对User_Setup.h中的TFT_DRIVER、TFT_WIDTH/HEIGHT、SDA/SCL、RESET等引脚定义。旋转编码器无反应或乱跳1. 引脚定义错误。2. 未启用上拉电阻。3. 库初始化或中断设置错误。4. 机械编码器质量差抖动严重。1. 用万用表或简单程序测试编码器A、B相在转动时的电平变化。2. 在代码中启用内部上拉pinMode(encoderPinA, INPUT_PULLUP)。3. 确保使用了正确的编码器库并按照示例正确初始化和设置中断。4. 尝试增大库中的消抖去抖参数。收不到任何电台只有噪音1. TEA5767 I2C通信失败。2. 频率设置错误PLL计算错。3. 天线未接或太短。4. 地区频段不对。1. 使用I2C扫描程序Arduino IDE示例中有检查地址0x60是否存在。2. 在设置频率后立即读取TEA5767的状态寄存器反算当前频率验证写入是否正确。3. 确保天线已连接并尝试更换位置、拉直。4. 中国FM广播为87.5-108MHz确保程序设置在此范围。有些模块默认是76-91MHz日本频段需通过配置寄存器切换。有台但声音失真、杂音大1. 信号太弱。2. 电源噪声干扰。3. 音频线受到干扰。4. TEA5767模块本身质量问题。1. 尝试调整天线方向和长度。2. 实施前述的电源滤波方案。3. 使用屏蔽线连接TEA5767的音频输出到PAM8403输入并尽量缩短走线。4. 检查TEA5767的音频输出耦合电容如果有是否合适。指针移动与频率变化不同步1. 编码器计数值到频率的映射公式错误。2. 屏幕绘制角度映射错误。3. 编码器加速逻辑导致步进不均匀。1. 在串口监视器中打印编码器值和计算出的频率确认映射关系。2. 打印计算出的角度值检查是否在预期的弧度或角度范围内。3. 暂时关闭编码器加速功能测试基础步进是否正确。5.3 功能扩展与进阶玩法基础功能实现后这个平台还有很大的扩展空间信号强度指示器读取TEA5767状态字节中的ADC值信号强度在表盘下方绘制一个模拟的“信号强度表”或LED灯条调台时会动态变化实用性趣味性兼备。预设电台记忆利用ESP32的SPIFFS或Preferences库将几个常听电台的频率保存起来。通过短按、长按编码器按钮来实现“存储当前频率到预设位1”和“跳转到预设位1”的功能。甚至可以在表盘上用小圆点标记出预设位。网络时钟与天气ESP32具备Wi-Fi功能。可以在不干扰主表盘的情况下在屏幕角落显示从NTP服务器获取的当前时间或者通过API获取天气信息并显示图标让复古收音机融入智能家居。音频输入切换增加一个音频输入选择电路如用CD4052模拟开关让这台设备不仅能听收音机还能作为一台蓝牙音频接收器通过ESP32的A2DP协议或AUX输入的有源音箱提升实用性。外壳与装饰3D打印的外壳可以打磨、喷漆贴上仿木纹贴皮或皮革纹理安装真正的复古旋钮需要适配编码器的轴。甚至可以找一块亚克力切割成圆形丝印上刻度作为屏幕的保护盖和装饰层质感能提升好几个档次。这个项目的魅力在于它从一个简单的想法出发融合了硬件设计、底层通信协议、嵌入式编程和UI交互最终呈现为一个有温度、可触碰的作品。每一次转动旋钮寻找电台的过程都是一次与过去时光的对话而背后稳定运行的则是现代嵌入式技术的坚实支撑。