news 2026/9/9 19:13:20

STM32驱动ADS1118:SPI接口实现多路ADC与热电偶测温完整教程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32驱动ADS1118:SPI接口实现多路ADC与热电偶测温完整教程

简介:基于STM32F103与STM32F407的ADS1118完整驱动方案,主要面向嵌入式开发者、电子竞赛学生以及从事高精度数据采集的工程师。程序覆盖4路单端、2路双差分和片内温度传感器三种采集模式,实现从寄存器初始化、SPI读写时序到多通道数据转换与错误处理的全流程封装。代码结构清晰,关键函数均附带注释,便于直接移植到实际项目,适合用于温度监测、压力采集或工业模拟量输入等场景。资源同时提供对应的实验测试数据,包含不同输入模式下的转换结果与温度读数,可借此校验驱动正确性,并了解芯片实际精度与噪声表现。配套的ADS1118技术手册与应用说明,能帮助读者深入理解16位ADC的配置逻辑、增益选择和采样率设定,是一份上手与进阶兼顾的实用资料。整套资源以RAR压缩包发布,大小约11.82MB,已吸引318人学习参考,对于希望快速掌握ADS1118开发的工程师和学生而言,具有较高的学习和参考价值。 做多路模拟量采集或者热电偶测温的时候,ADS1118 算是一颗绕不开的片子:16 位分辨率、内置 PGA、自带温度传感器,还通过 SPI 接口和 MCU 通信。但真开始写驱动才发现,网上能找到的例程大多是 ADS1115 的 I2C 版本,SPI 的 ADS1118 要么是寄存器片段,要么只支持单通道,连 STM32F103 和 STM32F407 的初始化差异都要自己慢慢试。前段时间我把这套驱动完整整理了一遍,覆盖 4 路单端、2 路差分和内部温度传感器三种模式,顺手把两代 STM32 的移植差异也理清了。这篇就按我的实际工程顺序讲一遍——哪些地方必须设置对、哪些坑我踩过之后不会再踩,照着抄基本能一次点亮。

1. 先从硬件说起:为什么 ADS1118 值得单独写一份驱动

ADS1118 是 TI 的 16 位 ΔΣ ADC,和更常见的 ADS1115 最大的区别是接口:ADS1115 走 I2C,ADS1118 走 SPI。很多人一开始会疑惑,同样是 16 位 ADC,为什么不用 I2C 版本?答案很简单,SPI 没有设备地址限制、速率更高,而且 ADS1118 的数据率最高能到 860SPS,做多通道轮询时比 I2C 舒服很多。更重要的是,ADS1118 内部集成了一个温度传感器,这颗传感器是专门为热电偶冷端补偿设计的。也就是说,一路差分接热电偶,同时读内部温度做冷端补偿,一颗芯片全干完了。

硬件接线不复杂,核心引脚就是标准的 SPI 四线:

ADS1118 引脚STM32F103STM32F407说明
SCLKPA5PA5SPI1 时钟
DINPA7PA7SPI1 MOSI,写命令字
DOUT/DRDYPA6PA6SPI1 MISO,读数据/转换完成指示
CSPA4PA4任意 GPIO,软件控制
VDD3.3V3.3V模拟和数字电源共用
GNDGNDGND必须共地

有两个硬件细节我建议一开始就处理好。第一,ADS1118 的输入绝对电压范围不能超过 VDD + 0.3V,即使 PGA 配置成 ±6.144V 满量程,输入引脚也扛不住 6V。如果你要测 5V 或者更高的电压,必须用电阻分压,把信号按比例降到 3.3V 以内。第二,DOUT/DRDY 这个引脚比较特殊,它既是数据输出,又是转换完成标志。有的板子 DOUT 悬空时电平不稳定,建议在 DOUT 上接一个 10kΩ 上拉到 3.3V,这样查询 DRDY 低电平时更可靠,也不会影响 SPI 时序。

至于电源去耦,ADS1118 的 AVDD 和 DVDD 可以接在一起,但芯片旁边至少要放一个 0.1µF 陶瓷电容,最好再并一个 10µF 钽电容。模拟输入如果来自高阻抗源,比如热电偶,信号线上加一个 RC 低通滤波会稳很多,后面实测部分我再细说。

2. 移植前必须先搞清楚的 SPI 初始化差异(F103 vs F407)

ADS1118 对 SPI 的时序要求很明确:CPOL=1、CPHA=1,也就是常说的 SPI Mode 3,数据帧必须是 16 位,高位在前,SCLK 最高 4MHz。这几个参数只要有一个不对,读回来的数据就是乱的。

为什么必须是 Mode 3?简单说,ADS1118 在 SCLK 的下降沿更新输出数据,主机在上升沿采样。SPI Mode 3 空闲时时钟为高,第一个边沿是下降沿,正好用来更新数据,第二个边沿是上升沿,主机采样。如果你配置成 Mode 0,采样沿和更新沿完全反过来,数据就错位了。这个错位不是每一次都错,偶尔能读出看似正常的数据,但会时不时跳变,非常难排查。

时钟分频也要算一下。ADS1118 的 SCLK 上限是 4MHz,STM32F103 的 SPI1 挂在 APB2 上,72MHz 主频下 16 分频是 4.5MHz,超了;STM32F407 的 SPI1 同样在 APB2,如果按 168MHz 主频、APB2 取 84MHz,16 分频是 5.25MHz,同样超了。所以两边我都用 32 分频,F103 实际 2.25MHz,F407 实际 2.625MHz,都落在安全范围内。实测下来这个速度对 ADS1118 完全够用,16 位数据一次事务也就几微秒。

初始化代码我用标准外设库写,F103 和 F407 的差异点主要在两处:RCC 使能时钟的写法相同,但 F407 需要额外调用 GPIO_PinAFConfig 把引脚复用成 SPI1 功能。SPI 外设本身的初始化参数,两套平台完全一致:

/* F103 与 F407 通用:SPI1 初始化 */ void ADS1118_SPI_Init(void) { SPI_InitTypeDef SPI_InitStructure; SPI_I2S_DeInit(SPI1); SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; /* Mode 3 */ SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; /* Mode 3 */ SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_Init(SPI1, &SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); }

GPIO 部分 F103 这样写:

void ADS1118_GPIO_Init_F103(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); /* SCK PA5, MOSI PA7 复用推挽输出 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); /* MISO PA6 浮空输入 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); /* CS PA4 普通推挽输出,默认拉高 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_SetBits(GPIOA, GPIO_Pin_4); }

F407 的差别就是复用映射:

void ADS1118_GPIO_Init_F407(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1); GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1); GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOA, &GPIO_InitStructure); /* CS 仍然普通 GPIO */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_SetBits(GPIOA, GPIO_Pin_4); }

CS 用普通 GPIO 控制,别用 SPI 的硬件 NSS。ADS1118 要求在每次 SPI 事务之间有完整的 CS 高电平恢复时间,软件手动控制最灵活,硬件 NSS 在多字节传递时会自动翻转电平,容易出问题。

3. ADS1118 的核心交互逻辑:写一次配置,读回上一轮数据

ADS1118 的配置寄存器一共 16 位,每次 SPI 事务,主机在 MOSI 上发送 16 位命令字,ADS1118 同时在 MISO 上把上一次的转换结果移出来。这个"边写边读旧值"的机制是所有驱动的关键,不理解它,代码写出来就是乱的。

配置寄存器各位的定义如下:

名称作用
bit15SS单次转换启动位,1 触发转换
bit14-12MUX输入通道选择
bit11-9PGA满量程/增益配置
bit8MODE1 单次模式,0 连续模式
bit7-5DR数据率选择
bit4TS1 启用内部温度传感器
bit3-0保留写 0

单次模式下的完整转换流程应该是:

  1. CS 拉低,发送带 SS=1 的配置字,启动一次转换。此时 MISO 移出的是上电默认值或者上一次转换的结果,这个数据不能要。
  2. CS 拉高,等待转换完成。等待时间由数据率决定,也可以用 DOUT/DRDY 引脚查询。
  3. CS 再次拉低,发送不带 SS 位的相同配置字,MISO 返回的就是本次转换结果。
  4. CS 拉高,解析数据。

为什么第二次发送配置字要不带 SS?因为 ADS1118 在单次模式下,SS=1 会再触发一次新转换。你只是想读当前结果,结果又启动了一次转换,逻辑就乱了。不带 SS 只是把相同配置重新写一遍,不触发转换,MISO 移出的正好是刚转换完的数据。

第一次读回的数据为什么不能要?很多人上电后第一次读,发现读回一个 0xFFFF 或者奇怪的固定值,以为是硬件问题。其实就是因为 MISO 移出的是旧数据,和当前输入信号一点关系都没有。这个不是故障,是芯片的设计行为。

转换等待时间直接查数据率:

数据率转换时间建议延时
8 SPS125ms130ms
16 SPS62.5ms70ms
32 SPS31.25ms40ms
64 SPS15.625ms20ms
128 SPS7.8ms10ms
250 SPS4ms6ms
475 SPS约 2.1ms4ms
860 SPS约 1.16ms2ms

延时是最简单的方案,适合绝大多数项目。如果想更精确,可以查询 DOUT/DRDY 引脚。这个引脚在转换完成时会输出低电平,检测到低电平再读数据,不需要留那么多裕量。但要注意,DOUT 就是 SPI 的 MISO 引脚,查询低电平时要把引脚从 SPI 复用模式切回普通输入模式,读完再切回去,代码会麻烦不少。我的经验是,除非对采样间隔有严格实时性要求,否则直接延时反而更省心。

4. 四路单端、两路差分和温度传感器的全部驱动代码

驱动文件我拆成 ads1118.h 和 ads1118.c 两部分,全部代码可以直接搬进工程。先看头文件,配置宏都定义在这里:

/* ads1118.h */ #ifndef __ADS1118_H #define __ADS1118_H #include "stm32f10x.h" /* 如果用 F407,改成 stm32f4xx.h */ /* 配置寄存器位定义 */ #define ADS1118_CFG_SS 0x8000u #define ADS1118_CFG_MUX_MASK 0x7000u #define ADS1118_CFG_MUX_DIFF_0_1 0x0000u #define ADS1118_CFG_MUX_DIFF_0_3 0x1000u #define ADS1118_CFG_MUX_DIFF_1_3 0x2000u #define ADS1118_CFG_MUX_DIFF_2_3 0x3000u #define ADS1118_CFG_MUX_AIN0 0x4000u #define ADS1118_CFG_MUX_AIN1 0x5000u #define ADS1118_CFG_MUX_AIN2 0x6000u #define ADS1118_CFG_MUX_AIN3 0x7000u #define ADS1118_CFG_PGA_6144 0x0000u #define ADS1118_CFG_PGA_4096 0x0200u #define ADS1118_CFG_PGA_2048 0x0400u #define ADS1118_CFG_PGA_1024 0x0600u #define ADS1118_CFG_PGA_512 0x0800u #define ADS1118_CFG_PGA_256 0x0A00u #define ADS1118_CFG_MODE_CONT 0x0000u #define ADS1118_CFG_MODE_SINGLE 0x0100u #define ADS1118_CFG_DR_8SPS 0x0000u #define ADS1118_CFG_DR_16SPS 0x0020u #define ADS1118_CFG_DR_32SPS 0x0040u #define ADS1118_CFG_DR_64SPS 0x0060u #define ADS1118_CFG_DR_128SPS 0x0080u #define ADS1118_CFG_DR_250SPS 0x00A0u #define ADS1118_CFG_DR_475SPS 0x00C0u #define ADS1118_CFG_DR_860SPS 0x00E0u #define ADS1118_CFG_TS 0x0010u /* 默认基础配置:PGA=2048,单次模式,128SPS */ #define ADS1118_BASE_CFG (ADS1118_CFG_PGA_2048 | ADS1118_CFG_MODE_SINGLE | ADS1118_CFG_DR_128SPS) #define ADS1118_CS_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_4) #define ADS1118_CS_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_4) void ADS1118_Init(void); uint16_t ADS1118_Transfer(uint16_t cmd); uint16_t ADS1118_ReadADC(uint16_t mux_cfg); int16_t ADS1118_ReadDiff(uint16_t mux_cfg); float ADS1118_ReadTemp(void); #endif

底层的 SPI 传输函数就是一次完整的 16 位收发,CS 包住整个事务:

/* ads1118.c */ #include "ads1118.h" void ADS1118_Init(void) { /* 调用前面写的 GPIO 和 SPI 初始化函数 */ ADS1118_GPIO_Init_F103(); /* 或 F407 */ ADS1118_SPI_Init(); } uint16_t ADS1118_Transfer(uint16_t cmd) { uint16_t rx = 0; ADS1118_CS_LOW(); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); SPI_I2S_SendData(SPI1, cmd); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); rx = SPI_I2S_ReceiveData(SPI1); ADS1118_CS_HIGH(); return rx; } static void ADS1118_WaitConvert(uint16_t cfg) { /* DR 编码对应数据率:8/16/32/64/128/250/475/860 SPS */ static const uint32_t wait_ms[8] = {130, 70, 40, 20, 10, 6, 4, 2}; uint8_t dr = (uint8_t)((cfg >> 5) & 0x07); delay_ms(wait_ms[dr]); }

单端和差分共用一个底层,区别只是 MUX 配置不同。读单端时原始值按无符号解析,读差分时按有符号二进制补码解析:

uint16_t ADS1118_ReadADC(uint16_t mux_cfg) { uint16_t cfg = ADS1118_BASE_CFG | (mux_cfg & ADS1118_CFG_MUX_MASK); /* 写配置并启动转换,此时读回的是旧数据,丢弃 */ ADS1118_Transfer(cfg | ADS1118_CFG_SS); /* 等待转换完成 */ ADS1118_WaitConvert(cfg); /* 重新写入相同配置,但不带 SS,读回本次转换结果 */ return ADS1118_Transfer(cfg); } int16_t ADS1118_ReadDiff(uint16_t mux_cfg) { uint16_t cfg = ADS1118_BASE_CFG | (mux_cfg & ADS1118_CFG_MUX_MASK); ADS1118_Transfer(cfg | ADS1118_CFG_SS); ADS1118_WaitConvert(cfg); return (int16_t)ADS1118_Transfer(cfg); }

温度读取更简单,关键是把配置里的 TS 位置 1,然后读回来的原始值除以 128.0 就是摄氏度:

float ADS1118_ReadTemp(void) { uint16_t cfg = (ADS1118_BASE_CFG & ~ADS1118_CFG_MUX_MASK) | ADS1118_CFG_TS; ADS1118_Transfer(cfg | ADS1118_CFG_SS); ADS1118_WaitConvert(cfg); return (float)((int16_t)ADS1118_Transfer(cfg)) / 128.0f; }

主程序里把三组数据全跑一遍,可以直观看到整个驱动的调用方式:

/* main.c 示例 */ const uint16_t mux_single[4] = { ADS1118_CFG_MUX_AIN0, ADS1118_CFG_MUX_AIN1, ADS1118_CFG_MUX_AIN2, ADS1118_CFG_MUX_AIN3 }; int main(void) { uint16_t single_raw[4]; int16_t diff_raw[2]; float temp; float voltage[4]; const float fsr = 2.048f; /* 和 BASE_CFG 里的 PGA_2048 对应 */ SystemInit(); ADS1118_Init(); while (1) { /* 4 路单端 */ for (int i = 0; i < 4; i++) { single_raw[i] = ADS1118_ReadADC(mux_single[i]); voltage[i] = (float)single_raw[i] * fsr / 32768.0f; } /* 2 路差分:AIN0-AIN1 和 AIN2-AIN3 */ diff_raw[0] = ADS1118_ReadDiff(ADS1118_CFG_MUX_DIFF_0_1); diff_raw[1] = ADS1118_ReadDiff(ADS1118_CFG_MUX_DIFF_2_3); /* 内部温度 */ temp = ADS1118_ReadTemp(); delay_ms(50); } }

电压换算公式就是电压 = 原始值 × 满量程 / 32768。PGA=2048 时满量程是 ±2.048V,所以 1 LSB 对应 62.5µV。差分通道的满量程和单端一样,只是原始值要按 int16 处理,负电压对应负值。

温度换算为什么除以 128?ADS1118 内部温度传感器实际是 14 位有效数据,左对齐到 16 位寄存器,最后两位固定为 0。14 位数据每个 LSB 是 0.03125°C,左对齐后相当于 16 位数据每个 LSB 是 0.0078125°C,也就是除以 128。这个细节网上很多代码都没讲清楚,直接给一个 magic number,现在知道原理就不用怕了。

5. 实测最容易踩的四个坑

5.1 重新配置量程后,第一笔数据到底能不能要

这个问题也是很多人问过的:如果我重新配置量程,需要等待多少时间?答案是:不仅要等转换完成,还要丢掉切换后的第一笔转换结果。ADS1118 内部有可编程增益放大器,切换 PGA 增益后,放大器建立需要时间。你写完新配置立刻启动转换,虽然 DRDY 也正常拉低,转换时序也完整,但增益切换瞬间引入的建立误差还没消失,这笔数据往往偏大或偏小,看起来像毛刺。

工程上的做法是切换量程或通道后,先做一次完整转换并丢弃,再采集有效值。写成一个函数就是:

uint16_t ADS1118_ReadADC_Stable(uint16_t mux_cfg) { ADS1118_ReadADC(mux_cfg); /* 第一次结果丢弃 */ return ADS1118_ReadADC(mux_cfg); /* 第二次结果作为有效值 */ }

如果你的采样循环一直在同一通道上跑,不需要每次都丢弃,只有切换配置的那一次需要处理。如果 4 路通道轮流采集,MUX 一直在切换,建议每路都按这个方式读,代价是采样周期变长,但数据可信度明显更高。

5.2 SPI Mode 用错后的症状

ADS1118 必须工作在 SPI Mode 3,也就是 CPOL=1、CPHA=1。我曾经为了在某个工程里和另一片 Flash 共用 SPI 总线,图省事把 CPOL 改成了 0,结果读出来的数据表现为:低位偶尔跳变、整数值偏差固定倍数、数据率和实际对不上。当时一度怀疑是 PCB 飞线太长干扰,后来单独用逻辑分析仪抓时序才发现是 SPI 模式不匹配。

判断 SPI 模式是否搞对,最直接的方法是切到 8 SPS 低速,读温度传感器。温度是缓慢变化的,如果连续读 20 次数值稳定、室温在合理范围,基本可以确认时序没问题。如果每次读出来数据乱跳,先查 Mode,别怀疑芯片。

5.3 DOUT 读不到数据的排查方向

读数据一直读到 0xFFFF 或者每次都一样,先检查三件事。第一,CS 拉低后有没有足够的建立时间,CS 下降沿到第一个 SCLK 上升沿之间至少要留出一个 SCLK 周期的时间,用软件控制时一般没问题,但如果 CS 引脚速度配置太慢,要留意。第二,DOUT 引脚是否浮空,ADS1118 的 DOUT/DRDY 在一些配置下需要上拉电阻,加了 10kΩ 上拉基本能解决。第三,确认 SPI 配置真的是 16 位数据帧末尾采样,如果错配成 8 位模式,一次事务只传输半个命令字,芯片根本不响应,读回的全是垃圾。

还有一个容易忽略的点:ADS1118 的 DOUT 在 CS 拉高时处于高阻态,如果 MISO 线上只有一个设备,问题不大;如果 MISO 上挂了多个 SPI 设备,务必确保其他设备的 CS 是拉高的,否则总线冲突会把数据拉坏。

5.4 采样稳定性与滤波建议

ADS1118 虽然分辨率高,但数据率越高,噪声越大。860SPS 下的有效分辨率明显不如 8SPS。实测 128SPS 是性价比最高的档位,响应速度和噪声控制都比较均衡。如果测的是温度这类缓变信号,建议降到 32SPS 或 16SPS,数据会稳很多。

硬件的 RC 滤波对抑制共模噪声很有用。输入引脚对地各接一个 100Ω 到 1kΩ 的串联电阻,再对地接 0.1µF 电容,能明显减少长线引入的毛刺。软件上做多次平均也有效,比如连续读 16 次取平均,代价是采样周期变长,但 128SPS 下 16 次平均仍然能保持每秒 7~8 次的更新率,绝大多数传感器场景都够用。我自己的项目里,128SPS 加 16 次平均,测 0~2V 的传感器信号,稳定度能做到最后一位基本不动,这个组合你们可以直接参考。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 19:12:57

AutoGPT 后端因 JWT_JWKS_URL 为明文 HTTP 拒绝启动怎么解决?

AutoGPT 后端因 JWT_JWKS_URL 为明文 HTTP 拒绝启动怎么解决&#xff1f; 【免费下载链接】AutoGPT AutoGPT is the vision of accessible AI for everyone, to use and to build on. Our mission is to provide the tools, so that you can focus on what matters. 项目地址…

作者头像 李华
网站建设 2026/9/9 19:12:25

STM32移植FreeModbus主机+FreeRTOS实战:工业数据采集与RS485通讯方案

简介&#xff1a;基于STM32F103ZET6的FreeModbus主机与FreeRTOS移植资源&#xff0c;面向嵌入式通信与物联网开发者&#xff0c;提供一套可对照实践的完整工程。压缩包共1254个文件&#xff0c;约24.23MB&#xff0c;涵盖612个C源文件、289个头文件以及汇编文件、目标文件、静态…

作者头像 李华
网站建设 2026/9/9 19:12:08

Audacity 多轨音频编辑指南:十分钟导入录音、降噪并导出

Audacity 多轨音频编辑指南&#xff1a;十分钟导入录音、降噪并导出 【免费下载链接】audacity Audio Editor 项目地址: https://gitcode.com/GitHub_Trending/au/audacity Audacity 是一款免费开源的多轨音频编辑与录音工具&#xff0c;支持 Windows、macOS 和 Linux。…

作者头像 李华
网站建设 2026/9/9 19:11:12

PHP版生产企业办公系统开发实战:从设计到部署的关键经验

简介&#xff1a;善翔PHP版生产企业办公系统是一套面向中小型制造与贸易企业的管理型PHP源代码资源&#xff0c;适合具备一定PHP开发基础的运维人员或二次开发学习者。系统采用PHP&#xff0b;Smarty模板引擎&#xff0b;MySQL实现&#xff0c;以模块化方式覆盖行政、业务与财务…

作者头像 李华