1. 课程导学与硬件准备
各位同学,欢迎来到《STM32 HAL库实战:移植WM8960音频驱动到自定义开发板》的第一章。在正式开始敲代码之前,咱们得先把家底儿摸清楚。说白了,做嵌入式驱动开发,七分靠硬件准备,三分靠代码。硬件没搞明白,后面调驱动就是瞎子摸象。
我个人习惯,拿到一个新项目,第一件事不是看数据手册,而是先看原理图和PCB。你得知道你的MCU和音频Codec之间是怎么连的,供电够不够,时钟干不干净。这一章,我们就来把这些准备工作做扎实。
1.1 WM8960芯片特性速览
WM8960是Cirrus Logic(原Wolfson)出品的一款低功耗立体声音频Codec。我在项目中用过不少音频芯片,WM8960算是比较“皮实”的一款,适合做便携式音频设备。
它的核心特性,我整理了一张表:
| 参数项 | 具体规格 | 我的备注 |
|---|---|---|
| 音频通道 | 立体声DAC + 立体声ADC | 支持同时录音和放音 |
| 采样率 | 8kHz ~ 96kHz | 常见48kHz足够用 |
| 位深 | 16/20/24/32 bit | I2S标准接口 |
| 控制接口 | I2C(从机模式) | 地址0x1A或0x1B |
| 音频接口 | I2S / Left-Justified / DSP | 我们只用I2S |
| 输出功率 | 1W @ 8Ω (单声道BTL) | 直推小喇叭没问题 |
| 工作电压 | DCVDD 2.7~3.6V / DBVDD 1.71~3.6V | 注意模拟和数字分开供电 |
重点记住:WM8960的控制接口是I2C,音频数据接口是I2S。这两个接口的时序,是我们后面移植工作的核心。
为什么会强调这个?因为我曾经在一个项目里,把I2C地址搞错了,结果调了两天I2C通信都调不通,最后发现是原理图上ADDR引脚的电平没拉对。嗯,这种低级错误,犯过一次就记住了。
1.2 自定义开发板硬件架构
我们这次用的开发板,是我自己设计的一块STM32F407VGT6核心板,外挂了WM8960。你想想看,如果直接用官方开发板,那移植工作就太“顺滑”了,学不到真东西。自定义板子才有挑战性。
先看整体架构图:
从图上可以看到,STM32和WM8960之间主要有三条“路”:
- I2C控制通路:用来配置WM8960的寄存器,比如音量、采样率、输入输出路由等。
- I2S音频数据通路:用来传输实际的PCM音频数据,这是音频流的“高速公路”。
- MCLK时钟通路:WM8960需要主时钟,我们用一个12.288MHz的晶振提供。这个频率是48kHz采样率的整数倍,很标准。
注意:MCLK必须精确。如果MCLK不准,WM8960内部PLL锁不住,出来的声音就会变调。我遇到过用内部RC振荡器当MCLK的情况,结果声音像磁带卡带了一样。
1.3 开发工具准备
工欲善其事,必先利其器。我们这次用到的工具链很标准:
1.3.1 STM32CubeMX
版本我用的是6.10.0,其实6.x系列都行。CubeMX的作用是帮我们生成HAL库的初始化代码,省去手动配置时钟树和GPIO的麻烦。我个人习惯,先用CubeMX把外设引脚和时钟配好,生成工程,再往里面加自己的驱动代码。
1.3.2 Keil MDK
版本5.38,ARMCC V6编译器。Keil的调试器很好用,尤其是硬件断点和实时变量查看。如果你用其他IDE,比如IAR或者STM32CubeIDE,也没问题,代码是通用的。
1.3.3 调试工具
- 逻辑分析仪:至少8通道,用来抓I2C和I2S时序。我推荐Saleae的逻辑分析仪,便宜好用。
- 万用表:测电压、测通断。别小看这个,很多问题都是虚焊或者供电不足引起的。
- 示波器:如果有条件,用示波器看MCLK和BCLK的波形,能发现时钟抖动和幅值问题。
我的小建议:刚开始调音频驱动时,别急着上音乐数据。先用逻辑分析仪抓I2C的寄存器读写,确认通信正常了,再放一个1kHz的正弦波测试音。这样能快速定位问题是出在控制通路还是数据通路。
1.4 搭建最小系统测试环境
所谓最小系统,就是让STM32能跑起来,并且能和WM8960建立基本的I2C通信。这一步成功了,后面的事情就顺了。
具体步骤:
- 焊接检查:用万用表测STM32和WM8960的电源对地电阻,确保没有短路。上电后测各点电压是否正常。
- 下载LED闪烁程序:先不管音频,让STM32的LED灯闪烁,证明MCU最小系统工作正常。
- I2C扫描:写一个I2C扫描程序,让STM32去扫描0x00~0x7F地址,看能不能找到WM8960(地址0x1A或0x1B)。
- 读取WM8960芯片ID:WM8960的寄存器0x00是芯片ID,复位值是0x004F。如果能正确读到这个值,说明I2C通信完全OK。
下面是一个简单的I2C扫描代码片段,我用HAL库写的:
// I2C扫描函数,检测WM8960是否存在
void I2C_Scan(void)
{
HAL_StatusTypeDef ret;
uint8_t i;
uint8_t pData = 0;
printf("I2C Scanning...\r\n");
for (i = 0x00; i < 0x7F; i++)
{
ret = HAL_I2C_IsDeviceReady(&hi2c1, (uint16_t)(i << 1), 1, 10);
if (ret == HAL_OK)
{
printf("Found device at 0x%02X\r\n", i);
}
}
printf("Scan done.\r\n");
}
// 读取WM8960芯片ID
uint16_t WM8960_ReadID(void)
{
uint8_t reg = 0x00;
uint8_t data[2] = {0};
HAL_I2C_Master_Transmit(&hi2c1, 0x34, ®, 1, 100);
HAL_I2C_Master_Receive(&hi2c1, 0x34, data, 2, 100);
return ((uint16_t)data[0] << 8) | data[1];
}
关键点:WM8960的I2C地址是7位地址0x1A,左移一位后变成0x34(写操作)和0x35(读操作)。HAL库的地址参数需要传入左移后的8位地址。
嗯,这里要注意。如果你扫描不到设备,别急着怀疑代码。先检查:
- I2C的上拉电阻有没有焊?一般4.7kΩ到10kΩ。
- WM8960的供电引脚电压对不对?DVDD和AVDD都要测。
- 原理图上ADDR引脚的电平,决定了I2C地址是0x1A还是0x1B。
我曾经在一个板子上,I2C死活扫不到设备,折腾了半天,最后发现是原理图上SCL和SDA画反了。嗯,这种低级错误,画PCB的时候一定要仔细核对。