TI AM57x + Linux SDK 的工业设备软件开发 + 附结构 部分开源



该设备是一套工业级精密打印/制造设备,整体采用高刚性框架结构,由运动平台、打印执行机构、真空及气路系统、控制系统和人机交互系统组成。机械部分采用多轴精密运动平台,通过伺服电机、直线导轨和滚珠丝杠实现高精度定位;打印头沿 X/Y/Z 多轴联动完成加工或打印作业,整体重心经过优化设计,以保证高速运动下的稳定性和加工精度。设备内部集成工业控制主板、电源、驱动器及各类传感器,可实现运动控制、工艺控制、数据采集和整机自动化运行,适用于工业级精密打印、材料加工及自动化制造等场景。
AM57X 与 MCU 开发对比
芯片定位
| 对比项 | MCU(如 STM32) | AM57X(AM5728/AM5748) |
|---|---|---|
| 类型 | 微控制器 | 应用处理器(MPU) |
| CPU | Cortex-M4/M7 | Cortex-A15(双核) |
| 主频 | 几十~几百 MHz | 1.0~1.5 GHz |
| 内存 | 片内 SRAM(KB 级) | 外挂 DDR3(GB 级) |
| 存储 | Flash(MB 级) | eMMC / SD / NAND |
| 操作系统 | 裸机 / RTOS | Linux |
| 启动时间 | 毫秒级 | 秒级(Linux 启动) |
软件层次对比
MCU(STM32 为例)
上电 └─→ 片内 ROM(检查 Boot 引脚) └─→ 你的 App(直接跑,裸机或 FreeRTOS)
只有一层:你的应用 = 整个软件
AM57X(Linux MPU)
上电 └─→ ROM Bootloader(片内固化,不可改) └─→ SPL(U-Boot 第一阶段) ├─→ 初始化 DDR3 └─→ U-Boot(第二阶段) ├─→ 加载 ATF(ARM Trusted Firmware) ├─→ 加载 OP-TEE(可信执行环境,可选) └─→ 加载 Linux Kernel └─→ 挂载 rootfs(文件系统) └─→ 你的 App(用户态进程)
有多层,每一层都需要单独编译和维护。
开发内容对比
| 开发内容 | MCU | AM57X |
|---|---|---|
| Bootloader | 通常不需要改 | 需要移植/配置 U-Boot + SPL |
| 操作系统 | 裸机 or FreeRTOS(可选) | Linux Kernel(必须) |
| 驱动 | HAL 库 / 寄存器直写 | Linux 内核驱动(字符设备、DTS) |
| 文件系统 | 无 | 需要构建 rootfs(Yocto / Buildroot) |
| 应用开发 | 直接在 main() 里写 | 标准 Linux 用户态程序(C/Python 等) |
| 调试方式 | JTAG / 串口打印 | JTAG + SSH + GDB + 串口 |
| 烧录 | ST-Link / J-Link 一键烧 | 分层烧录(U-Boot → Kernel → rootfs) |
开发工具链对比
| 工具 | MCU | AM57X |
|---|---|---|
| 编译器 | arm-none-eabi-gcc | arm-linux-gnueabihf-gcc |
| IDE | Keil / STM32CubeIDE / CCS | VS Code / CCS / 命令行 |
| 构建系统 | Make / CMake | Make / Yocto / Buildroot |
| 烧录工具 | ST-Link Utility / OpenOCD | SD 卡 / TFTP / USB DFU |
AM57X 特有的组件
| 组件 | 说明 |
|---|---|
| SPL | U-Boot 的第一阶段,体积极小,负责初始化 DDR |
| ATF | ARM Trusted Firmware,处理安全启动、电源管理 |
| OP-TEE | 可信执行环境,运行在安全世界(TrustZone) |
| Device Tree(DTS) | 描述硬件信息,替代 MCU 的 HAL 配置 |
| PRU-ICSS | 片上实时协处理器,运行工业协议(EtherCAT 等) |
| rootfs | 根文件系统,包含库、工具、你的 App |
AM57X 本 SDK 包含的内容
| 目录/文件 | 内容 |
|---|---|
U-Boot/ |
Bootloader 文档和构建指南 |
Kernel/ |
Linux 内核配置、驱动开发 |
Filesystem/ |
rootfs 构建(基于 Yocto) |
PRU-ICSS/ |
实时协处理器开发 |
Examples_and_Demos/ |
应用示例 |
Industrial_Protocols/ |
HSR/PRP、EtherCAT 等工业协议 |
开发流程(AM57X)
1. 搭建开发环境 ├─ 安装工具链(arm-linux-gnueabihf-gcc) ├─ 安装依赖包 └─ 配置 TFTP / NFS 服务 2. 编译 U-Boot(Bootloader) └─ 生成 MLO(SPL)+ u-boot.img 3. 编译 Linux Kernel └─ 生成 zImage + am57xx.dtb 4. 构建 rootfs └─ Yocto 构建 or 使用预编译的 5. 烧录到 SD 卡 / eMMC ├─ 分区:boot 分区 + rootfs 分区 └─ 拷贝对应文件 6. 开发你的 App └─ 交叉编译 → 拷贝到板子 → 运行
总结
-
MCU 开发:一个工程 = 整个系统,简单直接
-
AM57X 开发:分层开发,每层独立,像搭积木
-
U-Boot 负责"点火"
-
Kernel 负责"管理硬件"
-
rootfs 负责"提供环境"
-
App 负责"实现功能"
-
主机开发环境配置流程(替代 setup.sh)
配置结果总览
| 项目 | 路径/状态 |
|---|---|
| TFTP 根目录 | /tftpboot |
| NFS 共享目录 | /home/yewh/nfs |
| App 安装目录 | /home/yewh/nfs/home/root |
| 串口权限 | yewh 已加入 dialout 组 |
| TFTP 服务 | 开机自启,运行中 |
| NFS 服务 | 开机自启,运行中 |
第一步:安装依赖包
sudo apt install -y \ xinetd tftpd-hpa nfs-kernel-server \ libncurses5-dev dos2unix screen lzop flex u-boot-tools \ gcc-arm-linux-gnueabihf libssl-dev bc bison device-tree-compiler
第二步:串口权限
sudo usermod -aG dialout $USER # 注销重新登录后生效
第三步:配置 TFTP 服务
创建目录:
sudo mkdir -p /tftpboot sudo chmod 777 /tftpboot sudo chown -R $USER:$USER /tftpboot
修改配置文件 /etc/default/tftpd-hpa:
TFTP_USERNAME="tftp" TFTP_DIRECTORY="/tftpboot" TFTP_ADDRESS=":69" TFTP_OPTIONS="--secure --create"
重启并设置开机自启:
sudo systemctl restart tftpd-hpa sudo systemctl enable tftpd-hpa
验证:
systemctl is-active tftpd-hpa # 应显示 active
第四步:配置 NFS 服务
创建目录:
mkdir -p /home/yewh/nfs
修改配置文件 /etc/exports,添加:
/home/yewh/nfs *(rw,sync,no_subtree_check,no_root_squash) /tftpboot *(rw,sync,no_subtree_check,no_root_squash)
重启并设置开机自启:
sudo exportfs -ra sudo systemctl restart nfs-kernel-server sudo systemctl enable nfs-kernel-server
验证:
showmount -e localhost # 应显示: # /tftpboot * # /home/yewh/nfs *
第五步:更新 Rules.make
文件路径:
/home/yewh/SDK/PROCESSOR_SDK_AM57X/processor-sdk-linux-AM57X/Rules.make
修改以下三个变量:
EXEC_DIR?=/home/yewh/nfs/home/root # App 安装到 NFS 目录 ROOTFS_PART?=/home/yewh/nfs # rootfs 挂载点 BOOT_PART?=/tftpboot # boot 文件目录
验证所有配置
echo "=== TFTP ===" && systemctl is-active tftpd-hpa echo "=== NFS ===" && systemctl is-active nfs-kernel-server echo "=== 导出 ===" && showmount -e localhost echo "=== 目录 ===" && ls /tftpboot && ls /home/yewh/nfs
目录用途说明
| 目录 | 用途 |
|---|---|
/tftpboot |
板子上电后通过网络加载 Kernel/DTB 的地方 |
/home/yewh/nfs |
板子挂载的根文件系统(开发调试用) |
/home/yewh/nfs/home/root |
App 编译后 make install 自动放这里 |
板子端挂载 NFS 示例(板子 Linux 启动后执行):
mount -t nfs 192.168.1.x:/home/yewh/nfs /mnt/nfs
编译 U-Boot
编译环境
| 项目 | 值 |
|---|---|
| SDK 路径 | /home/yewh/SDK/PROCESSOR_SDK_AM57X/processor-sdk-linux-AM57X |
| U-Boot 源码 | board-support/ti-u-boot-2025.01+git/ |
| 交叉编译器 | linux-devkit/sysroots/x86_64-arago-linux/usr/bin/arm-oe-linux-gnueabi/arm-oe-linux-gnueabi- |
| defconfig | am57xx_evm_defconfig |
| 编译输出目录 | board-support/ti-u-boot-2025.01+git/build/a15/ |
一键编译
cd /home/yewh/SDK/PROCESSOR_SDK_AM57X/processor-sdk-linux-AM57X make u-boot
顶层 Makefile 实际执行了两步:
-
make am57xx_evm_defconfig— 生成.config -
make— 编译 U-Boot
编译产物
| 文件 | 大小 | 说明 |
|---|---|---|
build/a15/MLO |
146K | SPL(第一阶段引导,初始化 DDR,必须第一个写入 SD 卡) |
build/a15/u-boot.img |
1.6M | U-Boot 主程序(第二阶段,加载 Kernel) |
产物完整路径:
board-support/ti-u-boot-2025.01+git/build/a15/MLO board-support/ti-u-boot-2025.01+git/build/a15/u-boot.img
编译过程中遇到的依赖问题
| 缺失依赖 | 报错信息 | 解决 |
|---|---|---|
bison |
/bin/sh: 1: bison: not found |
sudo apt install -y bison |
libgnutls |
fatal error: gnutls/gnutls.h: No such file or directory |
sudo apt install -y libgnutls28-dev |
完整依赖一次性安装(避免反复出错):
sudo apt install -y \ build-essential libncurses5-dev libssl-dev \ bc flex bison device-tree-compiler \ u-boot-tools libgnutls28-dev
手动编译(不用顶层 Makefile)
如果需要单独配置和编译 U-Boot:
SDK=/home/yewh/SDK/PROCESSOR_SDK_AM57X/processor-sdk-linux-AM57X CROSS=$SDK/linux-devkit/sysroots/x86_64-arago-linux/usr/bin/arm-oe-linux-gnueabi/arm-oe-linux-gnueabi- UBOOT=$SDK/board-support/ti-u-boot-2025.01+git # 配置 make -C $UBOOT ARCH=arm CROSS_COMPILE=$CROSS am57xx_evm_defconfig O=$UBOOT/build/a15 # 图形化配置(可选,修改配置项) make -C $UBOOT ARCH=arm CROSS_COMPILE=$CROSS menuconfig O=$UBOOT/build/a15 # 编译 make -j$(nproc) -C $UBOOT ARCH=arm CROSS_COMPILE=$CROSS O=$UBOOT/build/a15
清理编译
cd /home/yewh/SDK/PROCESSOR_SDK_AM57X/processor-sdk-linux-AM57X make u-boot_clean
烧录到 SD 卡(板子到手后)
# 假设 SD 卡 boot 分区挂载在 /mnt/boot sudo cp board-support/ti-u-boot-2025.01+git/build/a15/MLO /mnt/boot/ sudo cp board-support/ti-u-boot-2025.01+git/build/a15/u-boot.img /mnt/boot/
U-Boot 启动流程
上电 └─→ ROM(片内,不可改) ├─ 检测启动介质(SD 卡 / eMMC / UART / 网络) └─→ 加载 MLO(SPL) ├─ 初始化 DDR3 内存 ├─ 初始化时钟 └─→ 加载 u-boot.img ├─ 初始化外设(网口、USB、串口) ├─ 读取 uEnv.txt 环境变量 └─→ 加载 zImage + DTB → 启动 Linux Kernel
AM57X Processor SDK 文件层级说明
一、当前目录总览
/home/yewh/SDK/PROCESSOR_SDK_AM57X/ │ ├── docs/ ← 【你自己写的笔记】 │ ├── 环境需求.md │ ├── AM57X与MCU开发对比.md │ └── SDK文件层级说明.md(本文件) │ ├── processor-sdk-linux-docs-AM57X/ ← 【官方离线文档】(纯资料,不是代码) ├── processor-sdk-linux-docs-AM57X.tar.gz ← 上面那个文件夹的压缩包 │ ├── ProcessorSDKLinux_11_01_manifest.html ← 【组件清单】列出所有软件包和版本 ├── AM57X_BuildSheet.xlsx ← 【构建配置表】 ├── AM5728开发板原理图.pdf ← 【硬件资料】 └── AM5749ABZX.rar ← 【待解压,内容待确认】
目前缺少的:U-Boot 源码、Linux Kernel 源码、rootfs 构建系统(需要下载)
二、官方文档结构(全是 HTML 资料,不是代码)
processor-sdk-linux-docs-AM57X/docs/ │ ├── index.html ← 文档首页入口 │ ├── linux/ ← 核心文档目录(Linux SDK 相关) │ │ │ ├── Overview/ ← 【入门必读】 │ │ ├── Processor_SDK_Linux_Getting_Started_Guide.html ← 快速入门指南 │ │ ├── Download_and_Install_the_SDK.html ← 如何下载安装 SDK │ │ ├── Processor_SDK_Linux_create_SD_card.html ← 制作 SD 卡启动盘 │ │ ├── Top_Level_Makefile.html ← 顶层 Makefile 说明 │ │ ├── Run_Setup_Scripts.html ← 运行安装脚本 │ │ ├── Program_EVM.html ← 烧录开发板 │ │ └── GCC_ToolChain.html ← 工具链说明 │ │ │ ├── Foundational_Components/ ← 【核心组件文档】(最重要) │ │ │ │ │ ├── U-Boot/ ← Bootloader 文档 │ │ │ ├── Users-Guide.html ← U-Boot 用户指南 │ │ │ ├── Build-Guide-OMAP.html ← 编译指南 │ │ │ ├── BG-Bootflow-OMAP.html ← 启动流程详解 │ │ │ ├── BG-Build-OMAP.html ← 构建说明 │ │ │ ├── BG-Setup-OMAP.html ← 环境配置 │ │ │ ├── BG-Target-Images-OMAP.html ← 目标镜像说明 │ │ │ ├── UG-DDR3.html ← DDR3 配置 │ │ │ ├── UG-NAND.html ← NAND Flash 使用 │ │ │ ├── UG-QSPI.html ← QSPI Flash 使用 │ │ │ ├── UG-Network.html ← 网络启动(TFTP) │ │ │ ├── UG-UART.html ← 串口启动 │ │ │ └── Applications.html ← U-Boot 应用 │ │ │ │ │ ├── Kernel/ ← Linux 内核文档 │ │ │ └── Kernel_Drivers/ ← 各外设驱动文档 │ │ │ ├── GPIO.html │ │ │ ├── I2C.html │ │ │ ├── SPI.html │ │ │ ├── UART.html │ │ │ ├── ADC.html │ │ │ ├── PWM.html │ │ │ ├── Audio.html │ │ │ ├── DCAN.html ← CAN 总线 │ │ │ ├── QSPI.html │ │ │ ├── UBIFS.html │ │ │ ├── VPE.html ← 视频处理 │ │ │ ├── Camera/ │ │ │ │ ├── VIP.html ← 摄像头输入 │ │ │ │ └── VPFE.html │ │ │ ├── Display/ │ │ │ │ ├── DSS.html ← 显示子系统 │ │ │ │ └── LCDC.html │ │ │ ├── Network/ │ │ │ │ ├── CPSW.html ← 以太网 │ │ │ │ ├── HSR_Offload.html │ │ │ │ └── PRUSS.html │ │ │ ├── Storage/ │ │ │ │ ├── MMC-SD.html ← SD 卡 │ │ │ │ ├── NAND.html │ │ │ │ └── SATA.html │ │ │ ├── USB/ │ │ │ │ └── DWC3.html │ │ │ ├── PCIe/ │ │ │ │ ├── PCIe_End_Point.html │ │ │ │ └── PCIe_Root_Complex.html │ │ │ └── Crypto/ │ │ │ └── SA2UL_OMAP.html ← 硬件加密 │ │ │ │ │ ├── Graphics/ ← GPU 图形文档 │ │ │ ├── Common/ │ │ │ │ ├── OpenGL_ES.html │ │ │ │ ├── Weston.html ← Wayland 合成器 │ │ │ │ ├── QT_Graphics_Framework.html │ │ │ │ ├── GTK+_Graphics_Framework.html │ │ │ │ └── Display.html │ │ │ └── SGX/ │ │ │ ├── Overview.html ← PowerVR SGX GPU 概述 │ │ │ └── Build_Guide.html │ │ │ │ │ ├── PRU-ICSS/ ← 实时协处理器文档 │ │ │ └── Linux_Drivers/ │ │ │ ├── RemoteProc.html ← 远程处理器框架 │ │ │ ├── RPMsg.html ← 核间通信 │ │ │ └── PRU-ICSS_Ethernet.html │ │ │ │ │ ├── Power_Management/ ← 电源管理 │ │ │ ├── pm_overview.html │ │ │ ├── pm_dvfs.html ← 动态电压频率调节 │ │ │ └── pm_suspend_resume.html │ │ │ │ │ ├── Tools/ ← 开发工具文档 │ │ │ ├── Code_Composer_Studio.html ← CCS IDE │ │ │ ├── Flash_Tools.html ← 烧录工具 │ │ │ └── Pin_Mux_Tools.html ← 引脚复用配置 │ │ │ │ │ ├── Foundational_Components_ATF.html ← ARM 可信固件 │ │ ├── Foundational_Components_OPTEE.html ← 可信执行环境 │ │ ├── Foundational_Components_IPC.html ← 核间通信(A15↔M4) │ │ ├── Foundational_Components_CMEM.html ← 连续内存分配 │ │ └── Foundational_Components_Filesystem.html ← 文件系统 │ │ │ ├── How_to_Guides/ ← 【操作指南】(怎么做) │ │ ├── Host/ ← 主机端操作 │ │ │ ├── Boot_Sequence.html ← 启动顺序 │ │ │ ├── How_to_Flash_Linux_System_from_U-boot.html │ │ │ └── Moving_Files_to_the_Target_System.html │ │ └── Target/ ← 板端操作 │ │ ├── How_to_Change_dtb_File.html │ │ └── Update_U-Boot_Environment_Variables_stored_in_SPI_Flash_from_Linux.html │ │ │ ├── Examples_and_Demos/ ← 【示例和演示】 │ │ └── Application_Demos/ │ │ ├── Dual_Camera_Demo.html ← 双摄像头演示 │ │ └── Video_Graphics_Test.html │ │ │ └── Industrial_Protocols/ ← 【工业协议】 │ ├── Industrial_Protocols_HSR_PRP.html │ ├── Industrial_Protocols_PTP.html ← 精确时间协议 │ └── Industrial_Protocols_OPCUA.html │ ├── common/ │ └── PRU-ICSS/ ← PRU 通用文档(跨平台) │ ├── Overview.html │ ├── PRU-Hands-on-Labs.html │ └── Getting_Started_with_PSSP.html │ └── devices/ └── AM57X/ └── linux/ ├── Release_Specific_Release_Notes.html ← 版本说明 ├── Release_Specific_Performance_Guide.html ← 性能指标 └── Release_Specific_Yocto_layer_Configuration.html ← Yocto 层配置
三、SDK 实际代码结构(已安装)
安装路径:/home/yewh/SDK/PROCESSOR_SDK_AM57X/processor-sdk-linux-AM57X/
processor-sdk-linux-AM57X/ │ ├── board-support/ ← 【板级代码】(最常修改的地方) │ ├── ti-u-boot-2025.01+git/ ← U-Boot 源码 │ ├── ti-linux-kernel-6.12.43+git-ti/ ← Linux Kernel 源码 │ ├── prebuilt-images/ ← 预编译好的镜像(可直接烧录) │ │ ├── MLO ← SPL(第一阶段引导) │ │ ├── u-boot.img ← U-Boot 镜像 │ │ ├── zImage ← Kernel 镜像 │ │ ├── am57xx-evm.dtb ← 设备树 │ │ └── boot-am57xx-evm.tar.gz ← boot 分区打包 │ └── extra-drivers/ ← 额外驱动 │ ├── linux-devkit/ ← 【交叉编译工具链】(不用改) │ └── sysroots/x86_64-arago-linux/usr/bin/arm-oe-linux-gnueabi/ │ └── arm-oe-linux-gnueabi-gcc ← 交叉编译器(GCC 13.4.0) │ ├── filesystem/ ← 预编译根文件系统 │ ├── tisdk-default-image-am57xx-evm.rootfs.tar.xz ← 完整版 rootfs │ ├── tisdk-base-image-am57xx-evm.rootfs.tar.xz ← 精简版 rootfs │ └── tisdk-thinlinux-image-am57xx-evm.rootfs.tar.xz │ ├── example-applications/ ← 示例应用源码 ├── yocto-build/ ← Yocto 构建系统 ├── setup.sh ← 环境初始化脚本 ├── Makefile ← 顶层 Makefile(一键编译) └── Rules.make ← 编译变量配置(工具链路径等)
四、你需要关注的文件(按开发顺序)
| 阶段 | 文档位置 | 对应代码 |
|---|---|---|
| 1. 搭建环境 | linux/Overview/Getting_Started_Guide.html |
无 |
| 2. 制作 SD 卡 | linux/Overview/create_SD_card.html |
无 |
| 3. 编译 U-Boot | linux/Foundational_Components/U-Boot/Build-Guide-OMAP.html |
board-support/u-boot-*/ |
| 4. 编译 Kernel | linux/Foundational_Components/Kernel/ |
board-support/linux-*/ |
| 5. 修改设备树 | linux/How_to_Guides/Target/How_to_Change_dtb_File.html |
linux/arch/arm/boot/dts/ |
| 6. 驱动开发 | linux/Foundational_Components/Kernel/Kernel_Drivers/ |
linux/drivers/ |
| 7. 应用开发 | linux/Examples_and_Demos/ |
example-applications/ |
五、一句话总结
| 目录 | 是什么 |
|---|---|
processor-sdk-linux-docs-AM57X/ |
纯文档,HTML 格式,用浏览器打开看 |
board-support/u-boot-*/ |
U-Boot 源码,需要编译 |
board-support/linux-*/ |
Kernel 源码,需要编译 |
linux-devkit/ |
编译工具链,不用改 |
filesystem/ |
预编译 rootfs,可直接用 |
example-applications/ |
示例 App 源码,参考用 |
AM57X Linux SDK 开发流程
总览
① 运行 setup.sh 配置主机开发环境(TFTP/NFS/串口) ↓ ② 制作 SD 卡启动盘 用预编译镜像,先让板子跑起来 ↓ ③ 验证板子正常启动 串口查看 U-Boot 和 Linux 启动日志 ↓ ④ 按需编译 U-Boot 修改 Bootloader 时才需要 ↓ ⑤ 按需编译 Kernel 修改驱动/设备树时才需要 ↓ ⑥ 开发自己的 App 交叉编译,通过 NFS 传到板子运行
第①步:运行 setup.sh 配置主机环境
setup.sh 会自动配置:TFTP 服务、NFS 服务、minicom 串口工具
cd /home/yewh/SDK/PROCESSOR_SDK_AM57X/processor-sdk-linux-AM57X ./setup.sh
脚本会询问:
-
SD 卡挂载路径(boot 分区 和 rootfs 分区)
-
TFTP 根目录
-
NFS 导出目录
配置完成后相关路径会写入 Rules.make 的 EXEC_DIR、BOOT_PART、ROOTFS_PART。
第②步:制作 SD 卡启动盘
SD 卡分区结构
SD 卡 ├── 分区1(FAT32,256MB,标记 boot) ← 存放引导文件 │ ├── MLO ← SPL 第一阶段引导(必须第一个) │ ├── u-boot.img ← U-Boot 第二阶段 │ ├── zImage ← Linux Kernel 镜像 │ ├── am57xx-evm.dtb ← 设备树(根据你的板子选) │ └── uEnv.txt ← U-Boot 环境变量 │ └── 分区2(ext4,剩余空间) ← 根文件系统 rootfs
预编译镜像位置
board-support/prebuilt-images/ ├── MLO ← 直接用 ├── u-boot.img ← 直接用 ├── zImage ← 直接用 ├── am57xx-evm.dtb ← 根据板子选对应 dtb └── uEnv.txt ← 直接用 filesystem/ └── tisdk-default-image-am57xx-evm.rootfs.tar.xz ← rootfs(解压到分区2)
分区和烧录步骤
1. 确认 SD 卡设备名(插入 SD 卡后)
lsblk # 假设 SD 卡是 /dev/sdb
2. 分区
sudo fdisk /dev/sdb # 删除所有分区 → 新建分区1(256M,FAT32,boot标记) → 新建分区2(剩余,ext4)
3. 格式化
sudo mkfs.vfat -F 32 -n "boot" /dev/sdb1 sudo mkfs.ext4 -L "rootfs" /dev/sdb2
4. 挂载并拷贝 boot 文件
sudo mount /dev/sdb1 /mnt/boot PREBUILT=/home/yewh/SDK/PROCESSOR_SDK_AM57X/processor-sdk-linux-AM57X/board-support/prebuilt-images sudo cp $PREBUILT/MLO /mnt/boot/ sudo cp $PREBUILT/u-boot.img /mnt/boot/ sudo cp $PREBUILT/zImage /mnt/boot/ sudo cp $PREBUILT/am57xx-evm.dtb /mnt/boot/ # 根据板子选对应 dtb sudo cp $PREBUILT/uEnv.txt /mnt/boot/ sudo umount /mnt/boot
5. 挂载并解压 rootfs
sudo mount /dev/sdb2 /mnt/rootfs FILESYSTEM=/home/yewh/SDK/PROCESSOR_SDK_AM57X/processor-sdk-linux-AM57X/filesystem sudo tar -xJf $FILESYSTEM/tisdk-default-image-am57xx-evm.rootfs.tar.xz -C /mnt/rootfs sudo umount /mnt/rootfs sync
6. 弹出 SD 卡,插入开发板上电
第③步:验证板子正常启动
串口连接
# 波特率 115200,无奇偶校验 minicom -D /dev/ttyUSB0 -b 115200
正常启动日志
# U-Boot 阶段 U-Boot SPL 2025.01 ... Trying to boot from MMC1 ... U-Boot 2025.01 ... Hit any key to stop autoboot: 3 # Kernel 阶段 Booting Linux on physical CPU 0x0 Linux version 6.12.43 ... ... # 登录 am57xx-evm login: root
第④步:编译 U-Boot(需要修改 Bootloader 时)
cd /home/yewh/SDK/PROCESSOR_SDK_AM57X/processor-sdk-linux-AM57X # 一键编译 make u-boot # 产物位置 # board-support/ti-u-boot-2025.01+git/build/a15/ # ├── MLO ← 新的 SPL # └── u-boot.img ← 新的 U-Boot
单独配置 U-Boot(menuconfig)
cd board-support/ti-u-boot-2025.01+git
make ARCH=arm CROSS_COMPILE=../../../linux-devkit/sysroots/x86_64-arago-linux/usr/bin/arm-oe-linux-gnueabi/arm-oe-linux-gnueabi- \
am57xx_evm_defconfig
make ARCH=arm CROSS_COMPILE=../../../linux-devkit/sysroots/x86_64-arago-linux/usr/bin/arm-oe-linux-gnueabi/arm-oe-linux-gnueabi- \
menuconfig
第⑤步:编译 Kernel(需要修改驱动或设备树时)
cd /home/yewh/SDK/PROCESSOR_SDK_AM57X/processor-sdk-linux-AM57X # 编译 Kernel + 设备树 make linux # 产物位置 # board-support/built-images/ # ├── zImage ← 新的内核镜像 # └── *.dtb ← 新的设备树
单独 menuconfig
cd board-support/ti-linux-kernel-6.12.43+git-ti CROSS=/home/yewh/SDK/PROCESSOR_SDK_AM57X/processor-sdk-linux-AM57X/linux-devkit/sysroots/x86_64-arago-linux/usr/bin/arm-oe-linux-gnueabi/arm-oe-linux-gnueabi- make ARCH=arm CROSS_COMPILE=$CROSS ti_multi_v7_prune.config make ARCH=arm CROSS_COMPILE=$CROSS menuconfig
第⑥步:开发自己的 App
交叉编译
# 设置交叉编译器
export CROSS_COMPILE=/home/yewh/SDK/PROCESSOR_SDK_AM57X/processor-sdk-linux-AM57X/linux-devkit/sysroots/x86_64-arago-linux/usr/bin/arm-oe-linux-gnueabi/arm-oe-linux-gnueabi-
export CC="${CROSS_COMPILE}gcc --sysroot=/home/yewh/SDK/PROCESSOR_SDK_AM57X/processor-sdk-linux-AM57X/linux-devkit/sysroots/armv7at2hf-neon-oe-linux-gnueabi"
# 编译
$CC -o my_app my_app.c
传到板子运行(NFS 方式,推荐)
# 主机:导出 NFS 目录 echo "/home/yewh/nfs *(rw,sync,no_root_squash)" | sudo tee -a /etc/exports sudo exportfs -ra # 板子端:挂载 NFS mount -t nfs 192.168.1.x:/home/yewh/nfs /mnt/nfs # 运行 /mnt/nfs/my_app
传到板子运行(SCP 方式)
scp my_app root@192.168.1.x:/home/root/
附:工具链路径(常用)
| 变量 | 路径 |
|---|---|
| SDK 根目录 | /home/yewh/SDK/PROCESSOR_SDK_AM57X/processor-sdk-linux-AM57X |
| 交叉编译器前缀 | linux-devkit/sysroots/x86_64-arago-linux/usr/bin/arm-oe-linux-gnueabi/arm-oe-linux-gnueabi- |
| sysroot | linux-devkit/sysroots/armv7at2hf-neon-oe-linux-gnueabi |
| U-Boot 源码 | board-support/ti-u-boot-2025.01+git/ |
| Kernel 源码 | board-support/ti-linux-kernel-6.12.43+git-ti/ |
| 预编译镜像 | board-support/prebuilt-images/ |
| rootfs | filesystem/tisdk-default-image-am57xx-evm.rootfs.tar.xz |
附:常用 DTB 对照表
| 板子型号 | 对应 DTB 文件 |
|---|---|
| AM572x IDK(AM5728)★ | am572x-idk-am57xx-evm.dtb ← 我们用这个 |
| AM571x IDK | am571x-idk-am57xx-evm.dtb |
| AM574x IDK | am574x-idk-am57xx-evm.dtb |
| AM57xx EVM | am57xx-evm-am57xx-evm.dtb |
| BeagleBone AI | am5729-beagleboneai-am57xx-evm.dtb |
设备树详解与修改指南 -- 硬件描述的语言
本篇目标:理解设备树的核心概念、语法结构,掌握修改和添加外设配置的方法
目录
什么是设备树
设备树(Device Tree)是一种描述硬件的数据结构,用来告诉 Linux 内核「这块板子上有哪些硬件、它们的地址是什么、怎么连接的」。
一句话解释
设备树就是板子的「硬件说明书」,内核读取它来知道该加载哪些驱动、怎么配置硬件。
设备树的角色
┌──────────────────────────────────────────────┐ │ Linux 内核 │ │ │ │ "这块板子有什么硬件?" │ │ "I2C 控制器在哪个地址?" │ │ "GPIO3[12] 接的是什么 LED?" │ │ "UART3 的引脚是哪两个?" │ │ │ │ ↓ 读取设备树获取答案 ↓ │ ├──────────────────────────────────────────────┤ │ 设备树(DTB 文件) │ │ │ │ am572x-idk.dtb │ │ ├── CPU: 2x Cortex-A15 @ 1.5GHz │ │ ├── 内存: 2GB DDR3 @ 0x80000000 │ │ ├── I2C1: 0x48070000, 400kHz, enabled │ │ ├── UART3: 0x48020000, 115200, enabled │ │ ├── GPIO3: 0x48057000, LED heartbeat │ │ ├── eHRPWM0: 0x4843e200, disabled │ │ └── ... │ ├──────────────────────────────────────────────┤ │ 硬件(PCB) │ └──────────────────────────────────────────────┘
设备树文件类型
| 扩展名 | 全称 | 说明 |
|---|---|---|
.dts |
Device Tree Source | 设备树源文件(顶层,描述具体板子) |
.dtsi |
Device Tree Source Include | 设备树包含文件(被 .dts 引用,描述 SoC 通用部分) |
.dtb |
Device Tree Blob | 编译后的二进制文件(内核实际使用) |
.dtso |
Device Tree Source Overlay | 设备树覆盖源文件(修改/扩展现有设备树) |
.dtbo |
Device Tree Blob Overlay | 编译后的覆盖二进制文件 |
为什么需要设备树
没有设备树的时代
在设备树出现之前(Linux 3.x 以前),每块开发板的硬件信息都硬编码在内核源码中:
/* 旧方式:板级文件(board file) */
/* arch/arm/mach-omap2/board-am572x-idk.c */
static struct i2c_board_info am572x_i2c1_boardinfo[] = {
{
I2C_BOARD_INFO("tps659038", 0x58),
.irq = gpio_to_irq(7),
},
};
static void __init am572x_i2c_init(void)
{
omap_register_i2c_bus(1, 400, am572x_i2c1_boardinfo,
ARRAY_SIZE(am572x_i2c1_boardinfo));
}
这种方式的问题:
-
每块新板子都要修改内核代码并重新编译
-
ARM SoC 种类爆炸式增长,内核代码中堆积了大量板级文件
-
同一个 SoC 的不同开发板,差异仅在于外设连接,却需要各写一套代码
有了设备树之后
硬件信息从内核代码中剥离出来,放到独立的设备树文件中:
-
内核代码只包含驱动程序(通用的,适用于所有使用该 SoC 的板子)
-
设备树文件描述具体的板子(哪些外设启用、怎么接线)
-
换一块板子?只需要换一个设备树文件,不用重编内核
MCU 初始化 vs 设备树
理解设备树最好的方式是跟 MCU 开发对比:
MCU(STM32 CubeMX)
CubeMX 图形界面 ↓ 生成 HAL 初始化代码 (main.c) ↓ 编译进 固件 (firmware.bin) ↓ 烧录到 MCU Flash
CubeMX 做的事情:选择引脚功能、配置时钟、设置外设参数 → 生成 C 代码。
Linux(设备树)
设备树源文件 (.dts) ↓ dtc 编译 设备树二进制 (.dtb) ↓ U-Boot 加载到内存 Linux 内核读取 ↓ 根据设备树 加载驱动、配置硬件
| 对比项 | MCU (CubeMX) | Linux (设备树) |
|---|---|---|
| 配置方式 | 图形界面拖拽 | 文本文件编辑 |
| 配置内容 | 时钟、引脚、外设参数 | 地址、中断、引脚、外设参数 |
| 输出格式 | C 源代码 | 二进制 DTB 文件 |
| 生效时机 | 编译时固化 | 启动时动态加载 |
| 修改成本 | 重新生成+编译固件 | 只需替换 DTB 文件 |
核心类比:设备树 ≈ CubeMX 生成的初始化代码,只不过它不是 C 代码,而是一种独立的数据格式。
设备树语法基础
基本结构
设备树使用类似 JSON 的层次结构:
/dts-v1/;
/ { /* 根节点 */
model = "TI AM5728 IDK"; /* 属性:字符串 */
compatible = "ti,am5728-idk"; /* 属性:兼容性标识 */
#address-cells = <2>; /* 属性:地址用几个 32-bit 值 */
#size-cells = <2>; /* 属性:大小用几个 32-bit 值 */
memory@80000000 { /* 子节点:内存 */
device_type = "memory";
reg = <0x0 0x80000000 0x0 0x80000000>; /* 2GB @ 0x80000000 */
};
i2c1: i2c@48070000 { /* 子节点:I2C1 控制器 */
compatible = "ti,omap4-i2c"; /* 内核用此匹配驱动 */
reg = <0x0 0x48070000 0x0 0x100>; /* 寄存器基地址和大小 */
clock-frequency = <400000>; /* 400kHz */
status = "okay"; /* 已启用 */
};
};
核心语法元素
1. 节点(Node)
/* 格式: [标签:] 节点名[@地址] { ... }; */
uart3: serial@48020000 {
/* 属性和子节点 */
};
-
uart3:— 标签(label),方便其他地方引用 -
serial— 节点名 -
@48020000— 单元地址(通常是寄存器基地址)
2. 属性(Property)
/* 字符串 */ compatible = "ti,omap4-i2c"; /* 整数 */ clock-frequency = <400000>; /* 整数数组 */ reg = <0x48070000 0x100>; /* 布尔(存在即为真) */ no-idle-on-init; /* 字符串列表 */ compatible = "ti,dra746-ehrpwm", "ti,am3352-ehrpwm"; /* 引用(phandle) */ pinctrl-0 = <&i2c1_pins>;
3. 关键属性说明
| 属性 | 说明 | 示例 |
|---|---|---|
compatible |
匹配驱动程序的标识 | "ti,omap4-i2c" |
reg |
设备寄存器地址和大小 | <0x48070000 0x100> |
status |
设备状态 | "okay" / "disabled" |
interrupts |
中断号和触发类型 | <GIC_SPI 88 IRQ_TYPE_LEVEL_HIGH> |
clocks |
时钟源引用 | <&l4per2_clkctrl ...> |
pinctrl-0 |
引脚复用配置引用 | <&i2c1_pins> |
4. compatible 属性详解
这是设备树中最重要的属性,内核通过它来匹配驱动:
/* 设备树中 */
ehrpwm0: pwm@4843e200 {
compatible = "ti,dra746-ehrpwm", "ti,am3352-ehrpwm";
/* 内核会按顺序匹配:先找 ti,dra746-ehrpwm 驱动,
找不到再找 ti,am3352-ehrpwm 驱动 */
};
/* 内核驱动中 */
static const struct of_device_id ehrpwm_of_match[] = {
{ .compatible = "ti,am3352-ehrpwm" },
{ .compatible = "ti,dra746-ehrpwm" },
{},
};
5. status 属性
| 值 | 含义 |
|---|---|
"okay" |
设备启用,内核会加载对应驱动 |
"disabled" |
设备禁用,内核忽略该节点 |
| 不写 | 默认为 "okay" |
这就是为什么前面的 GPIO、I2C、UART、PWM 案例中,启用外设就是改 status = "okay"。
AM5728 设备树文件结构
文件层次关系
am572x-idk.dts ← 你的板子!顶层文件 │ ├── #include "am572x-idk-common.dtsi" ← IDK 板通用配置 │ │ │ ├── #include "am5728.dtsi" ← AM5728 SoC 定义 │ │ │ │ │ ├── #include "dra74x.dtsi" ← DRA74x 系列通用 │ │ │ │ │ │ │ ├── #include "dra7.dtsi" ← DRA7 平台通用 │ │ │ │ │ │ │ │ │ └── #include "dra7xx-clocks.dtsi" ← 时钟定义 │ │ │ │ │ │ │ └── #include "dra74x-mmc-iodelay.dtsi" │ │ │ │ │ └── #include "am57-pruss.dtsi" ← PRU 子系统 │ │ │ └── 板级配置(LED、内存、外设状态等) │ └── 板级特定配置
各层负责什么
| 文件 | 层次 | 描述内容 |
|---|---|---|
dra7.dtsi |
SoC 平台 | CPU、GIC、所有外设控制器地址、中断号 |
dra74x.dtsi |
SoC 系列 | DRA74x 系列特有的外设 |
am5728.dtsi |
具体 SoC | AM5728 独有的配置(禁用不存在的模块) |
am572x-idk-common.dtsi |
开发板通用 | LED、内存、引脚复用、外设启用状态 |
am572x-idk.dts |
具体板子 | 最终的板级配置 |
关键理解
底层文件(dra7.dtsi)定义了所有可能的外设,状态默认为 disabled。上层文件(am572x-idk-common.dtsi)根据实际硬件选择性地将需要的外设改为 status = "okay"。
这就是为什么启用 PWM 时只需要写 &ehrpwm0 { status = "okay"; }; — 因为 ehrpwm0 的其他信息(地址、中断、时钟)已经在底层 dtsi 中定义好了。
引脚复用(Pinmux)
什么是引脚复用
AM5728 的每个物理引脚都可以配置为多种功能(最多 16 种模式,MODE0-MODE15)。例如同一个引脚可以是 UART TX、GPIO、PWM 输出、I2C SDA 等。
物理引脚 ─┬── MODE0: uart1_txd (UART1 发送)
├── MODE1: mmc4_sdcd (MMC4 卡检测)
├── MODE7: gpio7_24 (GPIO)
├── MODE10: ehrpwm0a (PWM 输出)
└── MODE14: driver_off (引脚禁用)
设备树中配置引脚复用
&dra7_pmx_core { /* 引脚复用控制器节点 */
i2c1_pins: i2c1-pins { /* 引脚组名称 */
pinctrl-single,pins = <
0x400 (PIN_INPUT | MUX_MODE0) /* i2c1_sda -> MODE0 */
0x404 (PIN_INPUT | MUX_MODE0) /* i2c1_scl -> MODE0 */
>;
};
ehrpwm0_pins: ehrpwm0-pins {
pinctrl-single,pins = <
0x360 (PIN_OUTPUT | MUX_MODE10) /* ehrpwm0A -> MODE10 */
>;
};
};
引脚配置值含义
偏移值 配置值 0x400 (PIN_INPUT | MUX_MODE0) │ │ │ │ │ └── 功能模式(0-15) │ └── 方向(PIN_INPUT / PIN_OUTPUT) └── 引脚在控制寄存器中的偏移地址
| 配置宏 | 值 | 说明 |
|---|---|---|
MUX_MODE0 ~ MUX_MODE15 |
0x0 ~ 0xF | 功能选择 |
PIN_INPUT |
0x100000 | 输入使能 |
PIN_OUTPUT |
0x0 | 输出模式(默认) |
PULL_UP |
0x40000 | 上拉使能 |
PULL_DISABLE |
0x20000 | 禁用上下拉 |
如何查找引脚偏移值
-
查阅 AM5728 技术参考手册(SPRUHZ6)的 "Control Module Pad Configuration" 章节
-
查看开发板原理图确认物理引脚连接
-
参考
dra7xx-pinfunc.h头文件(有预定义的引脚宏)
实战:启用外设
示例一:启用 I2C2
/* 步骤1:启用 I2C2 控制器 */
&i2c2 {
status = "okay";
clock-frequency = <400000>; /* 400kHz */
pinctrl-names = "default";
pinctrl-0 = <&i2c2_pins>;
};
/* 步骤2:配置引脚 */
&dra7_pmx_core {
i2c2_pins: i2c2-pins {
pinctrl-single,pins = <
0x408 (PIN_INPUT | MUX_MODE0) /* i2c2_sda */
0x40c (PIN_INPUT | MUX_MODE0) /* i2c2_scl */
>;
};
};
示例二:启用 eHRPWM0
/* 步骤1:启用父节点(EPWMSS0 子系统) */
&epwmss0 {
status = "okay";
};
/* 步骤2:启用 eHRPWM0 */
&ehrpwm0 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&ehrpwm0_pins>;
};
/* 步骤3:配置引脚 */
&dra7_pmx_core {
ehrpwm0_pins: ehrpwm0-pins {
pinctrl-single,pins = <
0x360 (PIN_OUTPUT | MUX_MODE10) /* ehrpwm0A */
0x364 (PIN_OUTPUT | MUX_MODE10) /* ehrpwm0B */
>;
};
};
示例三:启用 UART1
&uart1 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&uart1_pins>;
};
&dra7_pmx_core {
uart1_pins: uart1-pins {
pinctrl-single,pins = <
0x3e0 (PIN_INPUT | MUX_MODE0) /* uart1_rxd */
0x3e4 (PIN_OUTPUT | MUX_MODE0) /* uart1_txd */
>;
};
};
启用外设的通用步骤
1. 确认外设在底层 dtsi 中已定义(查看 dra7.dtsi / dra74x.dtsi) 2. 查阅数据手册确认引脚偏移值和 MUX_MODE 3. 在板级 dtsi 或 overlay 中: a. 设置 status = "okay" b. 配置引脚复用(pinctrl) c. 设置外设参数(时钟频率等) 4. 编译设备树 5. 部署并重启验证
设备树 Overlay
什么是 Overlay
Overlay(叠加层)允许在不修改原始设备树的情况下,动态添加或修改节点。就像在一张图上贴标签一样。
基础设备树(.dtb) Overlay(.dtbo)
┌─────────────────┐ + ┌─────────────────┐
│ ehrpwm0: │ │ &ehrpwm0 { │
│ status = │ → │ status = │
│ "disabled" │ │ "okay" │
│ │ │ }; │
└─────────────────┘ └─────────────────┘
= 最终效果
┌─────────────────┐
│ ehrpwm0: │
│ status = │
│ "okay" │
└─────────────────┘
Overlay 语法
/dts-v1/;
/plugin/; /* 必须有这行,表示这是 overlay */
/* 使用 & 引用已有节点并修改 */
&ehrpwm0 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&ehrpwm0_pins>;
};
/* 添加新的引脚配置 */
&dra7_pmx_core {
ehrpwm0_pins: ehrpwm0-pins {
pinctrl-single,pins = <
0x360 (PIN_OUTPUT | MUX_MODE10)
>;
};
};
Overlay vs 直接修改的对比
| 特性 | 直接修改 dtsi | Overlay |
|---|---|---|
| 需重编内核 | 是 | 否 |
| 可恢复性 | 需改回源码 | 删除 dtbo 即可 |
| 适用场景 | 永久配置 | 实验性修改、外接模块 |
| 管理难度 | 低(集中管理) | 中(多个 dtbo 文件) |
编译与部署
编译设备树(随内核编译)
cd /home/yewh/SDK/PROCESSOR_SDK_AM57X/processor-sdk-linux-AM57X # 编译所有设备树 make linux-dtbs # 部署到 rootfs make linux-dtbs_install
单独编译设备树
# 进入内核源码目录
cd processor-sdk-linux-AM57X/board-support/ti-linux-kernel-6.12.43+git-ti
# 编译单个 dts
make ARCH=arm CROSS_COMPILE=arm-oe-linux-gnueabi- dtbs
# 或手动用 dtc 编译
dtc -I dts -O dtb -o am572x-idk.dtb \
arch/arm/boot/dts/ti/omap/am572x-idk.dts
编译 Overlay
# 编译 overlay dtc -@ -I dts -O dtb -o my-overlay.dtbo my-overlay.dtso # -@ 选项表示保留符号信息(overlay 必需)
部署
# 复制 DTB 到 boot 分区 cp am572x-idk.dtb /boot/ # 或复制 overlay cp my-overlay.dtbo /boot/ # 配置 U-Boot 加载 overlay(在 uEnv.txt 中) # dtbo=my-overlay.dtbo
调试与验证
查看当前设备树
# 查看设备树目录结构 ls /proc/device-tree/ # 查看具体节点 ls /proc/device-tree/ocp/i2c@48070000/ # 读取属性值 cat /proc/device-tree/ocp/i2c@48070000/status # 输出: okay # 读取 compatible cat /proc/device-tree/ocp/i2c@48070000/compatible # 输出: ti,omap4-i2c
反编译 DTB
# 将编译后的 DTB 反编译成可读的 DTS dtc -I dtb -O dts -o output.dts am572x-idk.dtb # 查看完整的、展开后的设备树(包含所有 include) # 这对理解最终生效的配置非常有用
验证外设是否启用
# I2C ls /dev/i2c-* # UART ls /dev/ttyS* # PWM ls /sys/class/pwm/ # GPIO ls /dev/gpiochip* # LED ls /sys/class/leds/
内核启动日志
# 查看内核识别到的设备 dmesg | grep -i "i2c" dmesg | grep -i "pwm" dmesg | grep -i "serial" dmesg | grep -i "gpio" # 查看设备树加载信息 dmesg | grep -i "device.tree" dmesg | grep -i "Machine model"
常见问题详解
Q: 修改设备树后外设没有变化
排查步骤:
-
确认 DTB 已更新:编译后是否复制到了正确的 boot 位置?
-
确认 U-Boot 加载了正确的 DTB:在 U-Boot 控制台检查
fdtfile变量 -
确认语法正确:用
dtc -I dts -O dtb编译时没有报错? -
确认引脚没有冲突:同一个引脚不能同时用于两种功能
-
查看 dmesg:内核启动日志中是否有驱动加载失败的信息
Q: 引脚复用冲突怎么办?
AM5728 的每个物理引脚只能有一种功能。如果 UART1 和 PWM0 使用了同一个引脚,只能二选一。
解决方法:
-
查看原理图确认引脚走线
-
查看数据手册的引脚复用表
-
选择不冲突的引脚配置
Q: 设备树编译报 "syntax error"
常见语法错误:
-
节点末尾缺少
; -
属性值格式错误(如字符串没加引号)
-
引用未定义的标签
-
#include路径错误
Q: 如何知道某个外设的设备树节点名?
# 方法1:在 dra7.dtsi 中搜索 grep -r "ehrpwm" arch/arm/boot/dts/ti/omap/dra7*.dtsi # 方法2:查看运行中系统的设备树 ls /proc/device-tree/ocp/ # 方法3:反编译 DTB 搜索 dtc -I dtb -O dts am572x-idk.dtb | grep -A5 "ehrpwm"
本篇小结
知识点回顾
| 概念 | 说明 |
|---|---|
| 设备树 | 描述硬件的数据结构,替代硬编码的板级文件 |
.dts/.dtsi |
设备树源文件和包含文件 |
.dtb |
编译后的二进制设备树(内核使用) |
compatible |
最重要的属性,用于匹配内核驱动 |
status |
"okay" 启用,"disabled" 禁用 |
reg |
设备寄存器基地址和大小 |
pinctrl |
引脚复用配置 |
| Overlay | 不修改原始设备树的情况下叠加修改 |
设备树修改清单
启用一个外设通常需要:
□ 确认底层 dtsi 中有该外设的节点定义 □ 设置 status = "okay" □ 配置引脚复用(pinctrl) □ 设置外设参数(时钟频率、中断等) □ 编译 DTB 并部署 □ 重启验证
下一步学习
-
第12篇将介绍 Linux 内核模块与驱动开发入门
-
设备树是驱动开发的基础,理解设备树后才能写出正确的驱动
性能测试与系统调优
本篇目标:使用 SDK 自带的基准测试工具评估 AM5728 性能,掌握基本的系统调优方法
目录
为什么要做性能测试
在嵌入式开发中,性能测试有几个重要目的:
-
了解硬件基线:知道 CPU 的计算能力上限,才能合理分配任务
-
验证编译优化:确认交叉编译工具链的优化选项是否生效(如 NEON、Thumb2)
-
对比参考值:与 TI 官方数据对比,确认系统运行正常
-
指导设计决策:判断某个算法能否在规定时间内完成,是否需要使用 DSP/GPU
-
定位瓶颈:找出系统的性能瓶颈所在
AM5728 硬件规格回顾
┌─────────────────────────────────────────────┐ │ AM5728 SoC │ │ │ │ ┌──────────────────┐ ┌──────────────────┐ │ │ │ Cortex-A15 Core0 │ │ Cortex-A15 Core1 │ │ │ │ @ 1.5GHz │ │ @ 1.5GHz │ │ │ │ 32KB I$ + 32KB D$│ │ 32KB I$ + 32KB D$│ │ │ │ NEON + VFPv4 │ │ NEON + VFPv4 │ │ │ └────────┬─────────┘ └────────┬─────────┘ │ │ └──────┬──────────────┘ │ │ │ │ │ ┌──────┴──────┐ │ │ │ 2MB L2 Cache │ │ │ └──────┬──────┘ │ │ │ │ │ ┌───────────────┼───────────────┐ │ │ │ 2GB DDR3L @ 1066MHz │ │ │ └───────────────────────────────┘ │ │ │ │ 协处理器: │ │ ├── 2x C66x DSP @ 750MHz │ │ ├── 2x Cortex-M4 IPU @ 212MHz │ │ ├── 2x PRU-ICSS │ │ └── SGX544 GPU │ └─────────────────────────────────────────────┘
| 规格 | 参数 |
|---|---|
| CPU | 2x ARM Cortex-A15 @ 1.5GHz |
| L1 Cache | 32KB I-Cache + 32KB D-Cache (per core) |
| L2 Cache | 2MB 共享 |
| SIMD | NEON 128-bit |
| 浮点 | VFPv4 硬件浮点 |
| 内存 | 2GB DDR3L @ 1066MHz |
| 指令集 | ARMv7-A, 支持 Thumb2 |
Dhrystone:整数运算基准
什么是 Dhrystone
Dhrystone 是最经典的整数运算基准测试,测量 CPU 执行整数运算、字符串操作、数组访问等操作的速度。结果以 DMIPS(Dhrystone MIPS)表示。
源码位置
processor-sdk-linux-AM57X/example-applications/arm-benchmarks-1.3/dhrystone/ ├── dhry.h ├── dhry_1.c ├── dhry_2.c ├── Makefile ├── Debug/ └── Release/
编译与运行
cd processor-sdk-linux-AM57X/example-applications/arm-benchmarks-1.3/dhrystone # 编译(Debug 和 Release 版本) make all # Release 版本(有优化) make release
在板子上运行
# 运行 Dhrystone(Release 版本,有优化) ./Release/dhrystone # 输出示例: # Dhrystone Benchmark, Version 2.1 # Number of runs: 10000000 # ... # Dhrystones per second: XXXX # DMIPS: XXXX
理解结果
DMIPS = Dhrystones_per_second / 1757 理论参考值(Cortex-A15 @ 1.5GHz): 单核: ~5250 DMIPS (3.5 DMIPS/MHz) 双核: ~10500 DMIPS
Cortex-A15 的 Dhrystone 效率约 3.5 DMIPS/MHz,是 Cortex-A9 (2.5 DMIPS/MHz) 的 1.4 倍。
Debug vs Release 对比
| 版本 | 优化选项 | 预期性能 | 用途 |
|---|---|---|---|
| Debug | -O0 -g |
基线(较低) | 调试、验证正确性 |
| Release | -O2 |
最优 | 性能评估 |
对比 Debug 和 Release 的结果,可以直观看到编译器优化的效果。
Whetstone:浮点运算基准
什么是 Whetstone
Whetstone 测试浮点运算性能,包括 sin/cos/sqrt 等数学函数、数组操作、条件分支等。结果以 MWIPS(Million Whetstone Instructions Per Second)表示。
源码位置
processor-sdk-linux-AM57X/example-applications/arm-benchmarks-1.3/whetstone/
在板子上运行
./Release/whetstone # 输出示例: # Whetstone Benchmark # ... # MWIPS: XXXX
理解结果
AM5728 带有 VFPv4 硬件浮点单元和 NEON SIMD 引擎。如果 Whetstone 结果明显偏低,可能是编译时没有启用硬件浮点:
# 检查编译选项是否包含硬件浮点 # 正确的选项: -march=armv7-a -mfpu=neon -mfloat-abi=hard # 如果用了 -mfloat-abi=soft,浮点运算会用软件模拟,性能差很多
Linpack:线性代数基准
什么是 Linpack
Linpack 通过求解线性方程组来测试浮点运算能力,比 Whetstone 更侧重矩阵运算和内存带宽。这是超级计算机 TOP500 排名使用的基准。
源码位置
processor-sdk-linux-AM57X/example-applications/arm-benchmarks-1.3/linpack/
在板子上运行
./Release/linpack # 输出示例: # Linpack Benchmark # Matrix size: NxN # ... # MFLOPS: XXXX
理解结果
Linpack 结果受内存带宽影响很大。AM5728 的 DDR3L @ 1066MHz 理论带宽约 8.5GB/s,实际有效带宽取决于访问模式。
系统级性能测试
除了 CPU 基准测试,还需要了解系统整体性能:
内存带宽测试
# 使用 dd 测试磁盘/内存写入速度 dd if=/dev/zero of=/tmp/testfile bs=1M count=100 # 输出: xxx MB/s # 测试内存拷贝速度 dd if=/dev/zero of=/dev/null bs=1M count=1000
网络性能测试
# 在板子和主机上分别安装 iperf3 # 主机作为服务器 iperf3 -s # 板子作为客户端 iperf3 -c 192.168.1.100 # 测试结果通常接近千兆网卡理论值(~940Mbps)
CPU 频率确认
# 查看当前 CPU 频率 cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq # 输出: 1500000 (即 1.5GHz) # 查看可用频率 cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies # 查看调频策略 cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor # 常见: performance, ondemand, powersave
CPU 利用率监测
# 实时监测 top # 按核心显示(在 top 中按 '1') # 查看 CPU 详细信息 cat /proc/cpuinfo # 查看系统负载 cat /proc/loadavg
OProfile 性能分析
什么是 OProfile
OProfile 是一个系统级性能分析工具,可以统计程序在哪些函数上花费了最多的 CPU 时间。SDK 中包含了示例程序。
示例程序
processor-sdk-linux-AM57X/example-applications/oprofile-example/ └── signal_parent.c # 性能分析示例程序
该示例演示了父子进程通信和内存密集型操作,适合用 OProfile 分析性能热点。
基本使用
# 启动性能分析 operf ./my_program # 查看分析报告 opreport # 按函数查看 opreport -l # 按源码行查看(需要 -g 编译选项) opannotate --source
分析报告解读
CPU: ARM Cortex-A15, speed 1500 MHz Counted INST_RETIRED events samples % image name symbol name 15432 52.3% my_program calculate_fft 8213 27.8% libc.so memcpy 3421 11.6% my_program process_data 2456 8.3% [kernel] __copy_from_user
这个报告告诉你:52.3% 的时间花在 calculate_fft 函数,这是优化的首要目标。
omapconf 寄存器检测
什么是 omapconf
omapconf 是 TI 提供的 OMAP/AM SoC 寄存器检测和配置工具,可以查看时钟树、电源域、DPLL 配置等底层硬件状态。
源码位置
processor-sdk-linux-AM57X/example-applications/omapconf-1.75+git/ ├── omapconf.c # 主程序(34KB) ├── arch/arm/mach-omap/ │ ├── dra7/ # DRA7/AM57x 支持 │ ├── am335x/ # AM335x 支持 │ └── common/ # 通用功能 ├── pmic/ # PMIC 驱动 └── Makefile
常用命令
# 查看 SoC 信息 omapconf id # 查看时钟树 omapconf show clock # 查看电源域状态 omapconf show pwst # 查看 DPLL(PLL)配置 omapconf show dpll # 查看温度传感器 omapconf show temp # 读取特定寄存器 omapconf read 0x48070000
实用场景
| 场景 | 命令 | 说明 |
|---|---|---|
| 确认 CPU 频率 | omapconf show dpll |
查看 MPU DPLL 是否锁定到 1.5GHz |
| 检查外设时钟 | omapconf show clock |
确认 I2C/PWM 等外设时钟是否使能 |
| 查看芯片温度 | omapconf show temp |
监测 CPU 温度防止过热 |
| 调试启动问题 | omapconf show pwst |
检查电源域是否正常上电 |
系统调优基础
CPU 调频策略
# 设置为性能模式(固定最高频率)— 基准测试时使用 echo performance > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor echo performance > /sys/devices/system/cpu/cpu1/cpufreq/scaling_governor # 设置为按需调频(日常使用,省电) echo ondemand > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor # 设置为省电模式 echo powersave > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
基准测试时务必使用
performance模式,否则 CPU 可能运行在低频率,结果不准确。
CPU 亲和性(绑核)
# 将进程绑定到 CPU0 taskset -c 0 ./my_program # 将进程绑定到 CPU1 taskset -c 1 ./my_program # 用于避免进程在核心间迁移导致的性能波动
实时优先级
# 以实时优先级运行程序(减少调度延迟) chrt -f 50 ./my_program # -f: FIFO 调度策略 # 50: 优先级(1-99,越大越高)
关闭不必要的服务
# 查看运行中的服务 systemctl list-units --type=service --state=running # 关闭不需要的服务 systemctl stop bluetooth systemctl stop avahi-daemon # 减少后台进程对基准测试的干扰
内核参数调优
# 减少调度器抢占频率(提高吞吐量) echo 1 > /proc/sys/kernel/sched_child_runs_first # 调整虚拟内存参数 echo 10 > /proc/sys/vm/swappiness # 减少 swap 使用 # 查看中断分布 cat /proc/interrupts # 可以将特定中断绑定到特定 CPU echo 2 > /proc/irq/XX/smp_affinity # 绑定到 CPU1
常见问题详解
Q: 性能测试结果远低于预期
排查步骤:
-
检查 CPU 频率:是否在最高频率运行?
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
-
检查调频策略:是否为 performance?
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
-
检查编译优化:是否使用了
-O2和正确的架构选项?# 确认编译选项包含: -march=armv7-a -mthumb -mfpu=neon -mfloat-abi=hard -O2
-
检查系统负载:是否有其他进程占用 CPU?
top
-
检查温度:CPU 是否因过热降频?
cat /sys/class/thermal/thermal_zone*/temp
Q: 双核怎么用?
默认情况下,Linux 调度器会自动在两个核心之间分配任务。对于单线程程序,只能用到一个核心。
利用双核的方式:
-
多线程:使用 pthread 创建线程
-
多进程:fork 子进程
-
OpenMP:编译时加
-fopenmp
Q: NEON 加速怎么用?
NEON 是 ARM 的 128-bit SIMD 引擎,可以同时处理 4 个 32-bit 浮点或 16 个 8-bit 整数。
/* 方式1:编译器自动向量化(推荐) */
// 编译选项: -march=armv7-a -mfpu=neon -O2 -ftree-vectorize
for (int i = 0; i < N; i++)
c[i] = a[i] + b[i]; // 编译器可能自动用 NEON 优化
/* 方式2:NEON intrinsics(手动优化) */
#include <arm_neon.h>
float32x4_t va = vld1q_f32(a); // 一次加载 4 个 float
float32x4_t vb = vld1q_f32(b);
float32x4_t vc = vaddq_f32(va, vb); // 4 个加法同时完成
vst1q_f32(c, vc); // 一次存储 4 个 float
本篇小结
基准测试工具总结
| 工具 | 测试内容 | 结果单位 | 源码位置 |
|---|---|---|---|
| Dhrystone | 整数运算 | DMIPS | arm-benchmarks-1.3/dhrystone/ |
| Whetstone | 浮点运算 | MWIPS | arm-benchmarks-1.3/whetstone/ |
| Linpack | 矩阵运算 | MFLOPS | arm-benchmarks-1.3/linpack/ |
| OProfile | 性能热点分析 | 采样数/百分比 | oprofile-example/ |
| omapconf | SoC 寄存器状态 | 寄存器值 | omapconf-1.75+git/ |
性能调优清单
基准测试前: □ CPU 调频策略设为 performance □ 关闭不必要的后台服务 □ 确认编译优化选项正确(-O2, NEON, hard float) □ 确认 CPU 温度正常 性能优化方向: □ 编译器优化(-O2/-O3, -march, -mfpu) □ NEON SIMD 向量化 □ 多核并行(pthread, OpenMP) □ 缓存友好的数据结构 □ DMA 传输替代 CPU 拷贝 □ DSP 卸载计算密集任务
下一步学习
-
第14篇将是综合实战,把前面学到的知识综合运用
-
可以尝试用 DSP 核心进行计算加速(IPC 通信)
McASP 音频接口开发详解 -- ALSA 编程与音频采集播放
本篇目标:理解 AM5728 McASP 音频子系统和 Linux ALSA 框架,掌握音频采集与播放编程
目录
数字音频基础
声音的数字化
模拟声音 → 数字采样 → 数字处理 → 模拟输出 模拟波形 采样量化 ┌──────────┐ ┌──────────┐ │ /\ /\ │ ADC采样 │ ■ │ │/ \/ \ │ ──────────→ │ ■ ■ │ │ \│ │■ ■ ■ ■ │ └──────────┘ │ ■■ │ └──────────┘ 连续的声波 离散的数字值
关键参数
采样率(Sample Rate): 每秒采集的样本数,单位 Hz ┌────────────────────────────────┐ │ 8000 Hz 电话语音 │ │ 16000 Hz VoIP / 语音识别 │ │ 44100 Hz CD 音质 │ │ 48000 Hz DVD / 专业音频 │ │ 96000 Hz 高清音频 │ └────────────────────────────────┘ 位深度(Bit Depth / Sample Format): 每个样本的量化精度 ┌────────────────────────────────┐ │ 8-bit 256 级, 信噪比 48dB │ │ 16-bit 65536 级, SNR 96dB │ ← CD 标准 │ 24-bit 1677万级, SNR 144dB │ ← 专业录音 │ 32-bit 浮点, 内部处理用 │ └────────────────────────────────┘ 声道数(Channels): 1 = 单声道 (Mono) 2 = 立体声 (Stereo) N = 多声道 (环绕声/麦克风阵列) 数据率计算: 比特率 = 采样率 × 位深度 × 声道数 CD 音质: 44100 × 16 × 2 = 1,411,200 bps ≈ 1.41 Mbps
常见音频格式
| 格式 | 特点 | 典型场景 |
|---|---|---|
| PCM (原始) | 无压缩,最直接 | 硬件传输 |
| WAV | PCM + 文件头 | PC 录音 |
| MP3 | 有损压缩 ~128kbps | 音乐播放 |
| AAC | 有损压缩,比MP3更优 | 流媒体 |
| FLAC | 无损压缩 | 高品质音乐 |
| OPUS | 低延迟编码 | VoIP/实时 |
MCU vs Linux 音频对比
MCU(以 STM32 I2S + DAC 为例)
/* STM32 I2S 音频输出 */
// 1. 配置 I2S 参数
hi2s2.Instance = SPI2; // I2S2 共用 SPI2 外设
hi2s2.Init.Mode = I2S_MODE_MASTER_TX;
hi2s2.Init.Standard = I2S_STANDARD_PHILIPS;
hi2s2.Init.DataFormat = I2S_DATAFORMAT_16B;
hi2s2.Init.AudioFreq = I2S_AUDIOFREQ_44K;
HAL_I2S_Init(&hi2s2);
// 2. DMA 发送音频数据
HAL_I2S_Transmit_DMA(&hi2s2, (uint16_t *)audio_buf, buf_size);
// 3. DMA 完成回调中填充下一段数据
void HAL_I2S_TxHalfCpltCallback(I2S_HandleTypeDef *hi2s) {
fill_audio_buffer(audio_buf, buf_size / 2); // 前半部分
}
void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef *hi2s) {
fill_audio_buffer(audio_buf + buf_size/2, buf_size / 2); // 后半部分
}
Linux(ALSA)
/* Linux ALSA 音频播放 */ #include <alsa/asoundlib.h> // 1. 打开 PCM 设备 snd_pcm_t *pcm; snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK, 0); // 2. 配置参数 snd_pcm_set_params(pcm, SND_PCM_FORMAT_S16_LE, // 16-bit 小端 SND_PCM_ACCESS_RW_INTERLEAVED, // 交错模式 2, // 立体声 44100, // 44.1kHz 1, // 允许重采样 500000); // 延迟 500ms // 3. 写入音频数据 snd_pcm_writei(pcm, buffer, frames); // 4. 关闭 snd_pcm_drain(pcm); snd_pcm_close(pcm);
关键差异
| 维度 | MCU (STM32) | Linux (AM5728) |
|---|---|---|
| 音频接口 | I2S 直接操作 | ALSA 框架 |
| 设备标识 | I2S2/SPI2 外设 | "default"/"hw:0,0" 设备名 |
| 数据传输 | DMA 双缓冲回调 | snd_pcm_writei/readi |
| 参数设置 | HAL 结构体 | snd_pcm_set_params |
| 混音支持 | 无 | ALSA dmix 自动混音 |
| 音量控制 | DAC 寄存器 | amixer / alsamixer |
AM5728 McASP 资源
AM5728 McASP(Multichannel Audio Serial Port): ┌─────────────────────────────────────────────────────────┐ │ McASP1 ~ McASP8: 共 8 个音频串行端口 │ │ │ │ 每个 McASP 特性: │ │ ├── 支持 I2S / TDM / DIT(S/PDIF) 协议 │ │ ├── 最多 16 个序列化器(Serializer) │ │ │ 每个可独立配置为发送(TX)或接收(RX) │ │ ├── 支持 2~32 个 TDM 时隙 │ │ ├── 时钟: 内部(AUXCLK)或外部(AHCLK) │ │ ├── FIFO: 256字节 TX FIFO + 256字节 RX FIFO │ │ ├── DMA: 通过 EDMA3 高效传输 │ │ └── 兼容字符串: ti,dra7-mcasp-audio │ │ │ │ 典型连接: │ │ McASP → I2S → 音频编解码器(Codec) → 扬声器/麦克风 │ │ │ │ AM572x IDK 板上: │ │ McASP3 ── 连接音频 Codec(如 TLV320AIC3106) │ │ 提供耳机输出和麦克风输入 │ └─────────────────────────────────────────────────────────┘
McASP 与音频 Codec 的连接
AM5728 音频 Codec ┌──────────────┐ ┌──────────────┐ │ McASP3 │ │ TLV320AIC31xx│ │ │ │ │ │ ACLKX ────┼──── 位时钟 ───────→│ BCLK │ │ AFSX ────┼──── 帧同步 ───────→│ WCLK/LRCK │ │ AXR[0] ────┼──── 数据(TX) ─────→│ DIN │ │ AXR[1] ────┼──── 数据(RX) ←─────│ DOUT │ │ │ │ │ │ I2C ────┼──── 控制总线 ──────→│ I2C (配置) │ │ │ │ │ └──────────────┘ │ LINE OUT ──┼──→ 耳机/扬声器 │ LINE IN ──┼──← 麦克风/音源 │ HP OUT ──┼──→ 耳机 │ MIC IN ──┼──← 麦克风 └──────────────┘ 信号说明: ACLKX/BCLK: 位时钟,每个时钟传输 1 bit 数据 AFSX/WCLK: 帧同步(也叫 LRCK),区分左/右声道 AXR[n]: 数据线,可配置为发送或接收 I2C: 控制通道,配置 Codec 的采样率、增益等
Linux ALSA 框架架构
用户空间 ┌──────────────────────────────────────────────────┐ │ 应用程序 │ │ ├── aplay / arecord (命令行) │ │ ├── alsamixer (音量控制) │ │ ├── ALSA lib (libasound) │ │ │ snd_pcm_open / snd_pcm_writei │ │ └── 高级框架: PulseAudio / PipeWire / Jack │ └───────────────────┬──────────────────────────────┘ │ /dev/snd/* ▼ 内核空间 ┌──────────────────────────────────────────────────┐ │ ALSA Core (SoC 音频框架) │ │ sound/soc/soc-core.c │ │ │ │ ┌─────────┐ ┌──────────┐ ┌─────────────────┐ │ │ │ Platform │ │ Codec │ │ Machine │ │ │ │ Driver │ │ Driver │ │ Driver │ │ │ │ │ │ │ │ │ │ │ │ McASP │ │ TLV320 │ │ 板级绑定: │ │ │ │ DMA配置 │ │ AIC31xx │ │ McASP↔Codec │ │ │ │ I2S/TDM │ │ 增益/路由│ │ 时钟/格式配置 │ │ │ └─────┬────┘ └────┬─────┘ └────────┬────────┘ │ │ │ │ │ │ │ └────────────┴──────────────────┘ │ │ sound card │ └───────────────────┬──────────────────────────────┘ │ ┌───────────────────┴──────────────────────────────┐ │ McASP 硬件 ↔ Audio Codec ↔ 模拟音频 │ └──────────────────────────────────────────────────┘
ASoC 三层驱动模型:
| 层次 | 职责 | AM5728 示例 |
|---|---|---|
| Platform | CPU 侧音频接口和 DMA | McASP 驱动 + EDMA |
| Codec | 编解码器控制(增益/路由/采样率) | TLV320AIC31xx 驱动 |
| Machine | 板级连接关系(哪个 McASP 接哪个 Codec) | 设备树 sound 节点 |
ALSA 命令行工具实战
查看声卡信息
# 列出声卡 cat /proc/asound/cards # 0 [AM5728IDK ]: TLV320AIC3x - AM5728-IDK # AM5728-IDK # 列出 PCM 设备 aplay -l # card 0: AM5728IDK [AM5728-IDK], device 0: McASP [McASP3] # Subdevices: 1/1 # Subdevice #0: subdevice #0 # 列出录音设备 arecord -l # 查看 Codec 控制项 amixer -c 0 contents # 或交互式界面 alsamixer
音频播放与录制
# 播放 WAV 文件 aplay -D hw:0,0 test.wav # -D hw:0,0: 直接使用声卡0设备0(绕过 dmix) # 播放指定格式的原始 PCM 数据 aplay -D hw:0,0 -f S16_LE -r 48000 -c 2 raw_audio.pcm # -f: 格式 (S16_LE = 有符号16位小端) # -r: 采样率 # -c: 声道数 # 录制音频 arecord -D hw:0,0 -f S16_LE -r 48000 -c 2 -d 10 record.wav # -d 10: 录制 10 秒 # 环回测试(录制的同时播放) arecord -D hw:0,0 -f S16_LE -r 48000 -c 2 | aplay -D hw:0,0 # 调节音量 amixer -c 0 set 'PCM' 80% # 设置播放音量 amixer -c 0 set 'Line' 70% cap # 设置录音音量并启用 amixer -c 0 set 'Headphone' unmute # 取消耳机静音
生成测试音
# 生成正弦波测试音 speaker-test -D hw:0,0 -t sine -f 1000 -c 2 # -t sine: 正弦波 # -f 1000: 1kHz # -c 2: 立体声 # 生成粉噪 speaker-test -D hw:0,0 -t pink -c 2
工具一:WAV 音频播放
WAV 文件格式
WAV 文件结构: 偏移 长度 内容 ┌─────┬──────┬────────────────────────────┐ │ 0 │ 4 │ "RIFF" 标识 │ │ 4 │ 4 │ 文件大小 - 8 │ │ 8 │ 4 │ "WAVE" 标识 │ ├─────┼──────┼────────────────────────────┤ │ 12 │ 4 │ "fmt " 子块标识 │ │ 16 │ 4 │ fmt 块大小 (16) │ │ 20 │ 2 │ 音频格式 (1=PCM) │ │ 22 │ 2 │ 声道数 │ │ 24 │ 4 │ 采样率 │ │ 28 │ 4 │ 字节率 │ │ 32 │ 2 │ 块对齐 │ │ 34 │ 2 │ 位深度 │ ├─────┼──────┼────────────────────────────┤ │ 36 │ 4 │ "data" 子块标识 │ │ 40 │ 4 │ 数据大小 │ │ 44 │ ... │ PCM 音频数据 │ └─────┴──────┴────────────────────────────┘
源码:wav-play.c
/*
* wav-play.c -- WAV 文件播放器(使用 ALSA)
*
* 用法: ./wav-play <wav文件> [设备名]
* 示例: ./wav-play test.wav hw:0,0
*
* 编译需要: libasound2-dev
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <alsa/asoundlib.h>
/* WAV 文件头 */
#pragma pack(1)
struct wav_header {
char riff_id[4]; /* "RIFF" */
uint32_t riff_size;
char wave_id[4]; /* "WAVE" */
char fmt_id[4]; /* "fmt " */
uint32_t fmt_size;
uint16_t audio_format; /* 1 = PCM */
uint16_t channels;
uint32_t sample_rate;
uint32_t byte_rate;
uint16_t block_align;
uint16_t bits_per_sample;
char data_id[4]; /* "data" */
uint32_t data_size;
};
#pragma pack()
static snd_pcm_format_t bits_to_format(int bits)
{
switch (bits) {
case 8: return SND_PCM_FORMAT_U8;
case 16: return SND_PCM_FORMAT_S16_LE;
case 24: return SND_PCM_FORMAT_S24_3LE;
case 32: return SND_PCM_FORMAT_S32_LE;
default: return SND_PCM_FORMAT_UNKNOWN;
}
}
int main(int argc, char *argv[])
{
if (argc < 2) {
fprintf(stderr, "用法: %s <wav文件> [设备名]\n", argv[0]);
fprintf(stderr, "示例: %s music.wav hw:0,0\n", argv[0]);
return 1;
}
const char *filename = argv[1];
const char *device = (argc >= 3) ? argv[2] : "default";
/* 1. 读取 WAV 头 */
FILE *fp = fopen(filename, "rb");
if (!fp) {
perror("打开WAV文件失败");
return 1;
}
struct wav_header hdr;
if (fread(&hdr, sizeof(hdr), 1, fp) != 1) {
fprintf(stderr, "读取WAV头失败\n");
fclose(fp);
return 1;
}
/* 验证 WAV 格式 */
if (memcmp(hdr.riff_id, "RIFF", 4) != 0 ||
memcmp(hdr.wave_id, "WAVE", 4) != 0) {
fprintf(stderr, "不是有效的WAV文件\n");
fclose(fp);
return 1;
}
/* 跳过可能的额外 fmt 数据和非 data 块 */
if (memcmp(hdr.data_id, "data", 4) != 0) {
/* 简单处理: 向前搜索 "data" 标记 */
fseek(fp, 12 + 8 + hdr.fmt_size, SEEK_SET);
while (1) {
char chunk_id[4];
uint32_t chunk_size;
if (fread(chunk_id, 4, 1, fp) != 1) break;
if (fread(&chunk_size, 4, 1, fp) != 1) break;
if (memcmp(chunk_id, "data", 4) == 0) {
hdr.data_size = chunk_size;
break;
}
fseek(fp, chunk_size, SEEK_CUR);
}
}
if (hdr.audio_format != 1) {
fprintf(stderr, "仅支持 PCM 格式(当前格式: %d)\n", hdr.audio_format);
fclose(fp);
return 1;
}
printf("WAV 播放器\n");
printf(" 文件: %s\n", filename);
printf(" 设备: %s\n", device);
printf(" 采样率: %u Hz\n", hdr.sample_rate);
printf(" 位深: %u bit\n", hdr.bits_per_sample);
printf(" 声道: %u\n", hdr.channels);
printf(" 数据大小: %u 字节\n", hdr.data_size);
float duration = (float)hdr.data_size / hdr.byte_rate;
printf(" 时长: %.1f 秒\n", duration);
printf("---\n");
/* 2. 打开 ALSA PCM 设备 */
snd_pcm_t *pcm;
int err = snd_pcm_open(&pcm, device, SND_PCM_STREAM_PLAYBACK, 0);
if (err < 0) {
fprintf(stderr, "打开PCM设备失败: %s\n", snd_strerror(err));
fclose(fp);
return 1;
}
/* 3. 设置参数 */
snd_pcm_format_t format = bits_to_format(hdr.bits_per_sample);
if (format == SND_PCM_FORMAT_UNKNOWN) {
fprintf(stderr, "不支持的位深: %d\n", hdr.bits_per_sample);
snd_pcm_close(pcm);
fclose(fp);
return 1;
}
err = snd_pcm_set_params(pcm, format,
SND_PCM_ACCESS_RW_INTERLEAVED,
hdr.channels, hdr.sample_rate,
1, 100000); /* 允许重采样, 延迟100ms */
if (err < 0) {
fprintf(stderr, "设置参数失败: %s\n", snd_strerror(err));
snd_pcm_close(pcm);
fclose(fp);
return 1;
}
/* 4. 播放循环 */
int frame_size = hdr.channels * (hdr.bits_per_sample / 8);
int buf_frames = 1024;
int buf_bytes = buf_frames * frame_size;
uint8_t *buf = malloc(buf_bytes);
uint32_t total_played = 0;
printf("播放中...\n");
while (total_played < hdr.data_size) {
int to_read = buf_bytes;
if (total_played + to_read > hdr.data_size)
to_read = hdr.data_size - total_played;
int n = fread(buf, 1, to_read, fp);
if (n <= 0) break;
int frames = n / frame_size;
snd_pcm_sframes_t written = snd_pcm_writei(pcm, buf, frames);
if (written < 0) {
/* 尝试恢复 (underrun 等) */
written = snd_pcm_recover(pcm, written, 0);
if (written < 0) {
fprintf(stderr, "写入失败: %s\n", snd_strerror(written));
break;
}
}
total_played += n;
/* 显示进度 */
float pos = (float)total_played / hdr.byte_rate;
printf("\r %.1f / %.1f 秒 (%d%%)",
pos, duration, (int)(100.0 * total_played / hdr.data_size));
fflush(stdout);
}
printf("\n播放完成\n");
/* 5. 排空缓冲区并关闭 */
snd_pcm_drain(pcm);
snd_pcm_close(pcm);
free(buf);
fclose(fp);
return 0;
}
工具二:音频采集录制
源码:wav-record.c
/*
* wav-record.c -- WAV 录音工具(使用 ALSA)
*
* 用法: ./wav-record <输出文件> [时长秒] [设备名]
* 示例: ./wav-record out.wav 10 hw:0,0
*
* 编译需要: libasound2-dev
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <signal.h>
#include <alsa/asoundlib.h>
static volatile int running = 1;
static void sig_handler(int sig) { (void)sig; running = 0; }
#pragma pack(1)
struct wav_header {
char riff_id[4];
uint32_t riff_size;
char wave_id[4];
char fmt_id[4];
uint32_t fmt_size;
uint16_t audio_format;
uint16_t channels;
uint32_t sample_rate;
uint32_t byte_rate;
uint16_t block_align;
uint16_t bits_per_sample;
char data_id[4];
uint32_t data_size;
};
#pragma pack()
int main(int argc, char *argv[])
{
if (argc < 2) {
fprintf(stderr, "用法: %s <输出文件> [时长秒] [设备名]\n", argv[0]);
fprintf(stderr, "示例: %s record.wav 10 hw:0,0\n", argv[0]);
return 1;
}
const char *filename = argv[1];
int duration = (argc >= 3) ? atoi(argv[2]) : 0;
const char *device = (argc >= 4) ? argv[3] : "default";
/* 录音参数 */
unsigned int rate = 48000;
unsigned int channels = 2;
unsigned int bits = 16;
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
/* 1. 打开 ALSA 录音设备 */
snd_pcm_t *pcm;
int err = snd_pcm_open(&pcm, device, SND_PCM_STREAM_CAPTURE, 0);
if (err < 0) {
fprintf(stderr, "打开录音设备失败: %s\n", snd_strerror(err));
return 1;
}
/* 2. 设置参数 */
err = snd_pcm_set_params(pcm, SND_PCM_FORMAT_S16_LE,
SND_PCM_ACCESS_RW_INTERLEAVED,
channels, rate, 1, 100000);
if (err < 0) {
fprintf(stderr, "设置参数失败: %s\n", snd_strerror(err));
snd_pcm_close(pcm);
return 1;
}
/* 3. 创建输出文件并写入 WAV 头(数据大小后面更新) */
FILE *fp = fopen(filename, "wb");
if (!fp) {
perror("创建输出文件失败");
snd_pcm_close(pcm);
return 1;
}
struct wav_header hdr;
memcpy(hdr.riff_id, "RIFF", 4);
memcpy(hdr.wave_id, "WAVE", 4);
memcpy(hdr.fmt_id, "fmt ", 4);
hdr.fmt_size = 16;
hdr.audio_format = 1; /* PCM */
hdr.channels = channels;
hdr.sample_rate = rate;
hdr.bits_per_sample = bits;
hdr.block_align = channels * (bits / 8);
hdr.byte_rate = rate * hdr.block_align;
memcpy(hdr.data_id, "data", 4);
hdr.data_size = 0; /* 稍后更新 */
hdr.riff_size = 0; /* 稍后更新 */
fwrite(&hdr, sizeof(hdr), 1, fp);
printf("WAV 录音器\n");
printf(" 文件: %s\n", filename);
printf(" 设备: %s\n", device);
printf(" 采样率: %u Hz\n", rate);
printf(" 位深: %u bit\n", bits);
printf(" 声道: %u\n", channels);
if (duration > 0)
printf(" 时长: %d 秒\n", duration);
printf(" 按 Ctrl+C 停止录制\n");
printf("---\n");
/* 4. 录音循环 */
int frame_size = channels * (bits / 8);
int buf_frames = 1024;
uint8_t *buf = malloc(buf_frames * frame_size);
uint32_t total_bytes = 0;
uint32_t max_bytes = (duration > 0)
? (uint32_t)(duration * hdr.byte_rate) : UINT32_MAX;
printf("录音中...\n");
while (running && total_bytes < max_bytes) {
snd_pcm_sframes_t frames = snd_pcm_readi(pcm, buf, buf_frames);
if (frames < 0) {
frames = snd_pcm_recover(pcm, frames, 0);
if (frames < 0) {
fprintf(stderr, "录音错误: %s\n", snd_strerror(frames));
break;
}
}
int bytes = frames * frame_size;
if (total_bytes + bytes > max_bytes)
bytes = max_bytes - total_bytes;
fwrite(buf, 1, bytes, fp);
total_bytes += bytes;
float pos = (float)total_bytes / hdr.byte_rate;
printf("\r 已录制 %.1f 秒 (%u 字节)", pos, total_bytes);
fflush(stdout);
}
/* 5. 更新 WAV 头中的大小字段 */
hdr.data_size = total_bytes;
hdr.riff_size = total_bytes + sizeof(hdr) - 8;
fseek(fp, 0, SEEK_SET);
fwrite(&hdr, sizeof(hdr), 1, fp);
float final_duration = (float)total_bytes / hdr.byte_rate;
printf("\n---\n");
printf("录制完成: %.1f 秒, %u 字节\n", final_duration, total_bytes);
printf("文件: %s\n", filename);
snd_pcm_close(pcm);
free(buf);
fclose(fp);
return 0;
}
I2S/TDM 模式详解
I2S 模式
I2S(Inter-IC Sound)是最常用的数字音频接口:
BCLK ─┐┌─┐┌─┐┌─┐┌─┐┌─┐┌─┐┌─┐┌─┐┌─┐┌─┐┌─┐┌─┐┌─┐┌─┐┌─┐┌─
└┘ └┘ └┘ └┘ └┘ └┘ └┘ └┘ └┘ └┘ └┘ └┘ └┘ └┘ └┘ └┘
LRCK ─────────────────────┐ ┌─────
(WCLK) 左声道(L) └────── 右声道(R) ──────────┘
DATA ──┤MSB.............LSB┤MSB.............LSB┤──
←── 16/24/32 bit ──→←── 16/24/32 bit ──→
特点:
- 每帧 2 个声道(左/右)
- LRCK 低电平 = 左声道,高电平 = 右声道
- 数据在 BCLK 下降沿更新,LRCK 变化后延迟 1 个 BCLK
- BCLK 频率 = 采样率 × 位深 × 声道数
例: 48000 × 16 × 2 = 1.536 MHz
TDM 模式
TDM(Time Division Multiplexing)多声道时分复用:
BCLK ─┐┌─┐┌─┐┌─┐┌─┐┌─┐┌─┐┌─┐┌─┐┌─┐┌─┐┌─┐┌─┐┌─┐┌─┐┌─
└┘ └┘ └┘ └┘ └┘ └┘ └┘ └┘ └┘ └┘ └┘ └┘ └┘ └┘ └┘ └┘
FSYNC ─┐ ┌─
└────────────────────────────────────────────────┘
DATA ┤ Slot0 ┤ Slot1 ┤ Slot2 ┤ Slot3 ┤ ... ┤ SlotN ┤
特点:
- 一个帧同步周期内传输 N 个时隙(Slot)
- 每个时隙承载一个声道的数据
- 适合多声道(4/8/16/32声道)
- McASP 支持 2~32 个 TDM 时隙
- 工业应用: 麦克风阵列、多路ADC采集
设备树配置
音频系统设备树结构
/* 典型的 ASoC 音频设备树配置 */
/* 1. McASP 控制器(Platform Driver) */
&mcasp3 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&mcasp3_pins>;
op-mode = <0>; /* I2S 模式 */
tdm-slots = <2>; /* 2 个时隙(立体声) */
serial-dir = < /* 序列化器方向 */
1 2 0 0 /* 1=TX, 2=RX, 0=未使用 */
>;
tx-num-evt = <32>; /* TX FIFO 事件阈值 */
rx-num-evt = <32>; /* RX FIFO 事件阈值 */
};
/* 2. Audio Codec(Codec Driver) */
&i2c1 {
tlv320aic3106: audio-codec@18 {
compatible = "ti,tlv320aic3106";
reg = <0x18>; /* I2C 地址 */
ai3x-micbias-vg = <1>; /* 麦克风偏置电压 */
AVDD-supply = <&vdd_3v3>;
DRVDD-supply = <&vdd_3v3>;
IOVDD-supply = <&vdd_1v8>;
};
};
/* 3. 声卡绑定(Machine Driver) */
/ {
sound {
compatible = "simple-audio-card";
simple-audio-card,name = "AM5728-Audio";
simple-audio-card,format = "i2s"; /* I2S 格式 */
simple-audio-card,bitclock-master = <&dailink_master>;
simple-audio-card,frame-master = <&dailink_master>;
dailink_master: simple-audio-card,cpu {
sound-dai = <&mcasp3>;
system-clock-frequency = <24576000>; /* MCLK 频率 */
};
simple-audio-card,codec {
sound-dai = <&tlv320aic3106>;
clocks = <&audio_clk>;
};
};
};
McASP 引脚配置
&dra7_pmx_core {
mcasp3_pins: mcasp3-pins {
pinctrl-single,pins = <
/* mcasp3_aclkx (位时钟) */
DRA7XX_CORE_IOPAD(0x3724, PIN_OUTPUT | MUX_MODE0)
/* mcasp3_fsx (帧同步) */
DRA7XX_CORE_IOPAD(0x3728, PIN_OUTPUT | MUX_MODE0)
/* mcasp3_axr0 (数据输出 TX) */
DRA7XX_CORE_IOPAD(0x372c, PIN_OUTPUT | MUX_MODE0)
/* mcasp3_axr1 (数据输入 RX) */
DRA7XX_CORE_IOPAD(0x3730, PIN_INPUT | MUX_MODE0)
>;
};
};
常见问题详解
Q1: 找不到声卡设备?
# 检查声卡 cat /proc/asound/cards # 如果显示 "no soundcards found" # 检查内核模块 lsmod | grep -E "snd|mcasp|tlv320" modprobe snd-soc-davinci-mcasp modprobe snd-soc-tlv320aic3x # 检查设备树 ls /sys/bus/platform/devices/ | grep mcasp # 应看到 mcasp 设备 # 查看内核日志 dmesg | grep -iE "asoc|mcasp|codec|sound"
Q2: 播放时出现 underrun?
Underrun: 应用程序写入数据不够快,ALSA 缓冲区空了
解决方案:
1. 增大缓冲区
snd_pcm_set_params(..., 500000); // 延迟从 100ms 增到 500ms
2. 使用更大的写入块
buf_frames = 4096; // 增大每次写入的帧数
3. 提高进程优先级
nice -n -20 ./wav-play test.wav
# 或
chrt -f 50 ./wav-play test.wav
4. 处理恢复
if (frames < 0)
frames = snd_pcm_recover(pcm, frames, 0);
Q3: 如何调节音量?
# 命令行调节 amixer -c 0 set 'PCM' 80% # 播放音量 amixer -c 0 set 'Capture' 70% # 录音音量 amixer -c 0 set 'Headphone' 100 # 耳机音量(绝对值) # 查看所有控件 amixer -c 0 scontrols # Simple mixer control 'PCM',0 # Simple mixer control 'Headphone',0 # Simple mixer control 'Line',0 # Simple mixer control 'Capture',0 # 交互式图形界面 alsamixer -c 0 # 使用方向键调节,M 键切换静音
Q4: 如何交叉编译 ALSA 程序?
# Makefile CROSS_COMPILE ?= arm-linux-gnueabihf- CC = $(CROSS_COMPILE)gcc CFLAGS = -Wall -O2 -march=armv7-a LDFLAGS = -lasound # 如果 SDK sysroot 中有 ALSA 库 CFLAGS += --sysroot=$(SDK_SYSROOT) LDFLAGS += --sysroot=$(SDK_SYSROOT) TARGETS = wav-play wav-record all: $(TARGETS) wav-play: wav-play.c $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) wav-record: wav-record.c $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) clean: rm -f $(TARGETS)
本篇小结
知识要点回顾
McASP 音频开发知识图谱:
数字音频基础
├── 采样率 / 位深度 / 声道数
├── PCM / WAV 格式
└── 比特率计算
AM5728 McASP
├── 8 个 McASP 控制器
├── I2S / TDM / DIT 模式
├── 16 个序列化器(TX/RX可配)
└── EDMA DMA 传输
Linux ALSA 框架
├── ASoC 三层模型(Platform/Codec/Machine)
├── 命令行: aplay / arecord / amixer
├── API: snd_pcm_open / set_params / writei / readi
└── 错误恢复: snd_pcm_recover (underrun/overrun)
实战技能
├── WAV 文件解析与播放
├── 音频采集录制
├── 音量控制
└── 设备树 sound 节点配置
与前面章节的关联
| 章节 | 关联知识 |
|---|---|
| 第09篇 I2C | Codec 芯片通过 I2C 配置寄存器 |
| 第11篇 设备树 | sound 节点和 McASP 引脚配置 |
| 第13篇 性能测试 | DMA 传输效率影响音频质量 |
| 第15篇 SPI | 部分外置 ADC/DAC 通过 SPI 连接 |
整体系列回顾
至此,20篇 AM5728 嵌入式 Linux 开发学习文档已全部完成:
基础篇 (01-06): 环境搭建、编译部署 外设篇 (07-10): GPIO、UART、I2C、PWM(基础外设) 进阶篇 (11-14): 设备树、驱动、性能、综合实战 高级外设 (15-20): SPI、CAN、Ethernet、USB、Watchdog/Timer、McASP


从图中看,这套设备采用的是典型的工业级龙门/滑台式机械结构。下部是整机承重底座,使用大面积筋板和镂空框架设计,绿色部分像是主承力结构,灰色部分是安装板和加强板,通过多处开孔在减轻重量的同时保留结构刚性。底座右侧有一个较大的圆形回转或安装法兰结构,可能用于某个旋转机构、传动模组或关键部件安装。上部是运动执行平台,整体安装在底座导轨上方,能看到直线导轨、滑块、丝杠/传动轴、电机及连接件,说明该部分承担主要的直线运动和定位功能。图中还标出了“运动部重心”和“整体重心”,说明设计时重点考虑了运动部件相对整机的位置,避免上部运动机构过重、重心过高导致设备高速运行时产生振动、偏载或精度下降。右侧垂直区域类似打印头、喷头或加工头安装模块,旁边预留了安装板、加强肋和接口,便于后期更换执行头、传感器或视觉模块。整体来看,这不是简单外壳结构,而是一套面向工业打印/加工设备的高刚性、多轴运动机械平台,核心特点是:底座负责稳定承重,上部负责精密运动,导轨丝杠负责定位,模块化安装板负责扩展,重心优化负责提升运行稳定性。

更多推荐



所有评论(0)