第2章:开发环境搭建

好,咱们直接进入正题。开发环境搭建这事儿,说简单也简单,说复杂也复杂。我见过不少新手,一上来就急着写代码,结果编译都过不了,卡在工具链上浪费好几天。所以这一章,咱们把基础打牢。

2.1 交叉编译工具链安装

嵌入式开发,说白了就是在PC上写代码,编译成目标板能跑的程序。这个编译过程,就叫交叉编译。为什么叫“交叉”?因为你的开发环境(x86架构)和目标运行环境(ARM架构)不一样。

我个人习惯用Linaro提供的工具链,稳定,社区活跃。当然,你也可以用ARM官方的arm-none-eabi-gcc。这里我以ARM Cortex-M系列为例,演示一下安装过程。

核心要点:工具链版本一定要和你的目标芯片匹配。Cortex-M4和Cortex-M7的浮点单元不一样,选错了编译选项,程序跑起来会出诡异的问题。

# 下载ARM GCC工具链(以gcc-arm-none-eabi-10.3-2021.10为例)
wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/10.3-2021.10/gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2

# 解压到指定目录
tar -xjf gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2 -C /opt/

# 配置环境变量
export PATH=$PATH:/opt/gcc-arm-none-eabi-10.3-2021.10/bin

# 验证安装
arm-none-eabi-gcc --version

嗯,这里要注意。环境变量最好写到 ~/.bashrc 或者 /etc/profile 里,不然每次开终端都要重新 export,烦得很。

我的小技巧:我会在项目根目录下放一个 setenv.sh 脚本,里面写好所有环境变量。每次打开新终端,source一下就行。这样不同项目可以用不同版本的工具链,互不干扰。

2.2 目标板环境准备

目标板,就是咱们最终要跑语音合成引擎的硬件。ARM Cortex-M和Cortex-A系列,准备方式差别挺大。

2.2.1 Cortex-M系列(MCU类)

Cortex-M系列一般跑裸机或者RTOS。你需要准备:

  • 启动文件:芯片厂商提供的 startup_xxx.s,负责初始化堆栈、中断向量表
  • 链接脚本:.ld文件,定义Flash和RAM的地址空间
  • 调试工具:J-Link、ST-Link或者OpenOCD

我记得有一次,移植语音合成引擎到STM32F407上。启动文件用的是默认的,结果程序一跑就进HardFault。查了半天,发现是堆栈大小没改,默认的512字节根本不够语音合成用。后来我把堆栈改成4KB,问题解决。

避坑指南:我曾经因为链接脚本里Flash地址写错,导致程序下载进去后,一运行就死机。检查了三天,最后发现是芯片型号选错了,Flash地址偏移了0x1000。所以,链接脚本一定要对着芯片手册核对。

2.2.2 Cortex-A系列(MPU类)

Cortex-A系列一般跑Linux。你需要准备:

  • 交叉编译内核:用arm-linux-gnueabihf- 工具链编译Linux内核
  • 设备树:.dts文件,描述硬件资源
  • 根文件系统:Buildroot或者Yocto生成

你想想看,Cortex-A上跑语音合成,好处是内存大、算力强,但坏处是启动慢、功耗高。选型的时候要权衡。

2.3 文件系统与依赖库配置

语音合成引擎不是孤立的,它依赖一堆库。比如:

  • C标准库:newlib(裸机)或 glibc(Linux)
  • 数学库:libm,做音频信号处理用
  • 音频驱动库:I2S、DAC相关的底层库

2.3.1 裸机环境下的依赖配置

裸机环境下,没有动态链接,所有库都要静态编译进镜像。我建议用newlib,它比glibc小得多,适合资源受限的MCU。

# 编译时链接newlib
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb \
  -specs=nano.specs -specs=nosys.specs \
  -o main.elf main.c startup_stm32f407.s \
  -T stm32f407.ld -lm

这里 -specs=nano.specs 是告诉编译器用newlib的nano版本,体积更小。-specs=nosys.specs 是禁用系统调用,因为裸机没有操作系统。

关键点:语音合成引擎里有很多数学运算,比如正弦波生成、FFT变换。这些都需要数学库支持。如果发现编译报错说 sincos 未定义,记得加上 -lm

2.3.2 Linux环境下的依赖配置

Linux环境下,依赖库可以动态链接,也可以静态编译。我个人倾向动态链接,因为升级库方便,不用重新编译整个引擎。

# 交叉编译依赖库(以alsa-lib为例)
./configure --host=arm-linux-gnueabihf \
  --prefix=/opt/arm-libs/alsa
make
make install

# 编译语音合成引擎时指定库路径
arm-linux-gnueabihf-gcc -o tts_engine tts_engine.c \
  -I/opt/arm-libs/alsa/include \
  -L/opt/arm-libs/alsa/lib \
  -lasound -lm

嗯,这里有个坑。动态链接的库,运行时也要能找到。要么把库拷贝到目标板的 /usr/lib 下,要么用 LD_LIBRARY_PATH 指定路径。我建议直接拷贝到系统库目录,省心。

我的经验:我曾经在移植时,忽略了库的依赖顺序。链接时 -lasound -lm 写反了,结果链接报错。GCC的链接器是从右向左解析符号的,所以被依赖的库要放在右边。这个细节,坑过不少人。

2.4 验证环境是否搭建成功

环境搭好了,怎么验证?写个简单的Hello World,编译下载到目标板,看能不能跑起来。

#include <stdio.h>

int main() {
    printf("Hello, TTS Engine!\n");
    return 0;
}

如果目标板是裸机,printf的输出需要通过串口或者半主机模式(Semihosting)重定向到调试器。如果是Linux,直接打印到终端就行。

为什么会这样?因为裸机没有标准输出设备,你得告诉CPU,printf的内容往哪送。我一般用串口,简单可靠。

警告:半主机模式虽然方便,但会拖慢程序执行速度。语音合成引擎对实时性要求高,我建议在生产代码中禁用半主机,改用硬件UART直接输出。

2.5 本章小结

好,这一章的内容就这些。总结一下:

  • 交叉编译工具链要选对版本,环境变量要配好
  • 目标板环境,Cortex-M和Cortex-A准备方式不同,别搞混
  • 依赖库配置,裸机用newlib,Linux用动态链接
  • 验证环境,写个Hello World跑一跑

下一章,咱们开始讲语音合成引擎的代码结构。到时候,我会拿一个实际的开源引擎做例子,带大家一行一行看代码。准备好了吗?