该设备是一套工业级精密打印/制造设备,整体采用高刚性框架结构,由运动平台、打印执行机构、真空及气路系统、控制系统和人机交互系统组成。机械部分采用多轴精密运动平台,通过伺服电机、直线导轨和滚珠丝杠实现高精度定位;打印头沿 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 实际执行了两步:

  1. make am57xx_evm_defconfig — 生成 .config

  2. 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.makeEXEC_DIRBOOT_PARTROOTFS_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

设备树详解与修改指南 -- 硬件描述的语言

本篇目标:理解设备树的核心概念、语法结构,掌握修改和添加外设配置的方法


目录

  1. 什么是设备树

  2. 为什么需要设备树

  3. MCU 初始化 vs 设备树

  4. 设备树语法基础

  5. AM5728 设备树文件结构

  6. 引脚复用(Pinmux)

  7. 实战:启用外设

  8. 设备树 Overlay

  9. 编译与部署

  10. 调试与验证

  11. 常见问题详解

  12. 本篇小结


什么是设备树

设备树(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 禁用上下拉

如何查找引脚偏移值

  1. 查阅 AM5728 技术参考手册(SPRUHZ6)的 "Control Module Pad Configuration" 章节

  2. 查看开发板原理图确认物理引脚连接

  3. 参考 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: 修改设备树后外设没有变化

排查步骤:

  1. 确认 DTB 已更新:编译后是否复制到了正确的 boot 位置?

  2. 确认 U-Boot 加载了正确的 DTB:在 U-Boot 控制台检查 fdtfile 变量

  3. 确认语法正确:用 dtc -I dts -O dtb 编译时没有报错?

  4. 确认引脚没有冲突:同一个引脚不能同时用于两种功能

  5. 查看 dmesg:内核启动日志中是否有驱动加载失败的信息

Q: 引脚复用冲突怎么办?

AM5728 的每个物理引脚只能有一种功能。如果 UART1 和 PWM0 使用了同一个引脚,只能二选一。

解决方法:

  1. 查看原理图确认引脚走线

  2. 查看数据手册的引脚复用表

  3. 选择不冲突的引脚配置

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 性能,掌握基本的系统调优方法


目录

  1. 为什么要做性能测试

  2. AM5728 硬件规格回顾

  3. Dhrystone:整数运算基准

  4. Whetstone:浮点运算基准

  5. Linpack:线性代数基准

  6. 系统级性能测试

  7. OProfile 性能分析

  8. omapconf 寄存器检测

  9. 系统调优基础

  10. 常见问题详解

  11. 本篇小结


为什么要做性能测试

在嵌入式开发中,性能测试有几个重要目的:

  1. 了解硬件基线:知道 CPU 的计算能力上限,才能合理分配任务

  2. 验证编译优化:确认交叉编译工具链的优化选项是否生效(如 NEON、Thumb2)

  3. 对比参考值:与 TI 官方数据对比,确认系统运行正常

  4. 指导设计决策:判断某个算法能否在规定时间内完成,是否需要使用 DSP/GPU

  5. 定位瓶颈:找出系统的性能瓶颈所在


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: 性能测试结果远低于预期

排查步骤:

  1. 检查 CPU 频率:是否在最高频率运行?

    cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
  2. 检查调频策略:是否为 performance?

    cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
  3. 检查编译优化:是否使用了 -O2 和正确的架构选项?

    # 确认编译选项包含:
    -march=armv7-a -mthumb -mfpu=neon -mfloat-abi=hard -O2
  4. 检查系统负载:是否有其他进程占用 CPU?

    top
  5. 检查温度: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 框架,掌握音频采集与播放编程


目录

  1. 数字音频基础

  2. MCU vs Linux 音频对比

  3. AM5728 McASP 资源

  4. Linux ALSA 框架架构

  5. ALSA 命令行工具实战

  6. 工具一:WAV 音频播放

  7. 工具二:音频采集录制

  8. I2S/TDM 模式详解

  9. 设备树配置

  10. 常见问题详解

  11. 本篇小结


数字音频基础

声音的数字化

模拟声音 → 数字采样 → 数字处理 → 模拟输出
​
  模拟波形                    采样量化
  ┌──────────┐              ┌──────────┐
  │ /\  /\   │   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

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

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐