如何使I2S在STM32 MCU上运行

我本周已经在这个地方待了一段时间,在最终成功之后,我想出了一个超快速的大纲来帮助其他人开始并工作。 我不喜欢巨型/不透明的IDE和构建环境,因此这是设置简单的基于makefile的解决方案的方法。

尽管ST编写了支持它的库,但我在MBED社区的任何STM32处理器上都找不到适用的I2S示例。 如果您在流行的处理器上使用流行的功能,则朝着“ arduino式”的发展迈进了一大步,但是,如果您希望为该系列中较不常见的变体提供完整的功能支持,那么在MBED的高度抽象的世界中,事情就变得难以快速地解决。 通过对构建过程的完全控制和完全可见性,生活会更好。

成功的秘诀是STM32CubeMX软件,该软件仅在Windows(gro吟)上可用。 我是Mac用户,因此需要我的最终设置:

1.在Mac(https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads)上安装最新的arm-none-eabi-gcc-我认为Brew可以使用此工具,但是我最终直接从GNU下载,因为当我尝试使用brew时,它已经过期了)

2.在Mac上安装stlink实用程序 (https://github.com/texane/stlink-brew的最新安装)

3. Windows VM上的STM32CubeMX (底部的http://www.st.com/en/development-tools/stm32cubemx.html)

首先,我们将在STM32中获得基本的I2S音频。 我从Adafruit的SPH0645 I2S MEMs麦克风突破板开始,以及ST的STM32F767ZI Nucleo板开始(该板具有嵌入式STLink编程器,之所以被选中是因为正在进行一些很酷的工作,我想将tensorflow移植到它上) 。 为了正确设置板,我们需要将BCLK连接到PA5(CN7-10),将WS / LRCLK连接到PA4(CN7-17),将SD / DOUT连接到PA7(CN7-14)。 在此板上,我们必须提防— PA7被重复用于一些其他事情。 您必须删除JP6上的默认跳线,否则将无法接收任何数据。最后,我们将SEL线和GND接地,并将电源引脚上的Nucleo接头上的电压提高到3.3V。

现在是时候启动多维数据集,选择我们的处理器,并开始生成我们的默认项目。 这是三个步骤:

完成上述步骤后,您只需单击“项目>设置>生成代码”,我们就可以开始比赛了。

我唯一需要做的编辑是Makefile中arm / gcc工具的路径前缀(我个人将它们放在默认路径中)。 否则,您应该能够导航到该文件夹​​,键入“ make”,并成功编译项目。

一旦完成,我们可以使用简单的代码编辑main.c while循环:

uint16_t数据;

而(1){

HAL_StatusTypeDef结果= HAL_I2S_Receive(&hi2s1,&data,1,100);

}

运气好的话,您只需键入:

st-flash写入build / .bin 0x8000000

成功!

好吧,也许“成功”还为时过早。 我们正在音频线上看到数据,但这并不意味着我们会在固件中以有用的格式获取数据。 让我们实际看看发生了什么。

首先让我们看看会发生什么。 当环境安静时,麦克风会报告上面显示的相当一致的值。 我们期望有24个MSB对齐的位(尽管每个数据表中只有18个具有信息),应该使用C中的标准32位带符号int(一个long或typedef-ed int32_t)来方便地正确处理这些位。 是的,最后8-14位是垃圾,但这并不重要。 对于第一个值,我们在逻辑分析器上看到1111 1000 1011 1001 0100 0000 = 0xF8 0xB9 0x40,因此我们将在调试器中寻找该值。

让我们编辑while循环,以实际读取所需的全部数据:

uint16_t data_in [2];

而(1){

volatile HAL_StatusTypeDef结果= HAL_I2S_Receive(&hi2s1,data_in,2,100);

如果(结果== HAL_OK){
易失性int32_t data_full =(int32_t)data_in [0] << 16 | data_in [1];
volatile int16_t data_short =(int16_t)data_in [0];

volatile uint32_t计数器= 10;
while(counter —);
}

}

我不是最出色的C程序员,因此在运行此类初始测试时,我会随意地使用“ volatile”,因为我们测试的变量经常并没有被有意义地使用(我们只是在此处读取它们)点),因此编译器可能会对其进行优化。 您也可以在makefile的编译步骤中添加一个标志来防止这种情况。 制作并刷新此新版本后,让我们启动调试工具。 在一个窗口中,我们启动GDB调试服务器。 stlink工具为我们处理了所有杂乱的工作。

实用程序

在新窗口中,我们需要连接到GDB服务器以启动交互式调试会话:

arm-none-eabi-gdb -tui -eval-command =”目标扩展远程本地主机:4242” build / .elf

进入GDB后,输入ctl + x ,如果看不到漂亮的代码可视化,则输入a 。 ( ctl + x ,然后2添加一个很好的可视化的汇编指令,如果您对此也有兴趣的话)。

然后输入b main ,一旦您进入main函数调用,它将放置一个断点。 从那里可以很容易地在顶部控制台中识别您想在其上中断的行,并通过行号添加更多的断点(对于行116, b 116b main.c: 116)。 输入c将继续到下一个断点,并且如果进入循环,则cntl-c将停止操作(但会将您带到SIGTRAP中断向量,而不是最有用的位置)。

进入主程序后,我发现自己使用n来逐步执行每个命令。 要检查我们的变量,我们首先键入set output-radix 16以使其以十六进制(仅一次)打印,然后键入info locals

你说什么 看起来不正常吗? 好吧,当然不是。 第一次尝试没有任何效果,这是嵌入式编程。

我们注意到的是,似乎一次调用了I2S接收函数,但是随后我们陷入了每个连续循环的I2S超时I2S库函数调用中。 这并不是完全出乎意料的-超时和看门狗定时器之类的东西在我们以蜗牛般的步伐逐步执行代码时,倾向于将其破坏。

幸运的是,STM32具有一个称为“调试冻结”的功能来解决此问题。 在HAL_INIT调用之前的主函数中,添加以下宏:

__HAL_DBGMCU_UNFREEZE_IWDG();

重新编译,刷新并重新启动调试工具后,您应该发现所有行为都表现得更好。

现在我们可以合理地逐步执行功能了,我们应该期望I2S返回一些超时,一些零值(当读取左/右字时钟的右字时,因为I2S本质上是立体声),并且有些值接近于我们在逻辑分析仪的数据线上看到了预期的0xF8 0xB9 0x40。 经过一些步骤,我们终于得到了我们一直在寻找的确认信息:

0xF8 EB C0非常接近我们的期望! 看起来一切正常,并且我们能够以几种适当的格式(带符号的16位和32位整数)读取数据。

这个例子花了更多的时间和更多的理智去运行,而不是我愿意承认的。 我希望它能为您节省一些。

这是其他人的来之不易的STM32技巧的一个很好的集合。 这个家伙知道怎么了。 当您找到自己的STM32特质和绊脚石时,请为我们所有人提供帮助并做出贡献。