ATCP 是一个纯 C 语言实现的音频信道数据通信协议栈库。它通过标准 3.5mm 音频接口(Speaker Out / Mic In)在 PC 与 MCU 之间建立双向数据通道,实现绕过网络层的隐蔽通信。
核心特性:
- 零外部依赖,纯 C99,可嵌入任意平台
- 支持编译为静态库或动态库(DLL/SO)
- 下行差分双声道 OFDM + 自适应 QAM(QPSK ~ 256-QAM)
- 上行单声道固定 QPSK
- Reed-Solomon 前向纠错 + 滑动窗口 ARQ 双重可靠性保障
- 非阻塞事件驱动 API,适配裸机/RTOS/桌面环境
- CMake >= 3.10
- C99 兼容编译器(MSVC / GCC / Clang)
静态库(推荐嵌入式场景):
cmake -B build -DBUILD_SHARED_LIBS=OFF
cmake --build build --config Release动态库(DLL/SO):
cmake -B build -DBUILD_SHARED_LIBS=ON
cmake --build build --config Release静态库模式下,建议在你的项目中定义 ATCP_STATIC 宏以消除导出符号装饰。
CMake add_subdirectory 方式:
add_subdirectory(path/to/ATCP/lib)
target_link_libraries(your_app PRIVATE atcp)手动链接:
- 头文件目录:
lib/include - 库文件:
atcp.lib/libatcp.a/atcp.dll
#include <atcp/atcp.h>
/* ---- 1. 实现平台回调 ---- */
int my_audio_write(const float *samples, int n_samples, int n_channels, void *ud) {
/* 将采样写入声卡输出 / DAC */
return n_samples; /* 返回实际写入数 */
}
int my_audio_read(float *samples, int n_samples, int n_channels, void *ud) {
/* 从麦克风输入 / ADC 读取采样 */
return n_samples; /* 返回实际读取数 */
}
uint32_t my_get_time_ms(void *ud) {
/* 返回当前毫秒级时间戳 */
return get_system_time_ms();
}
/* ---- 2. 创建实例并通信 ---- */
int main(void) {
/* 配置平台回调 */
atcp_platform_t platform = {0};
platform.audio_write = my_audio_write;
platform.audio_read = my_audio_read;
platform.get_time_ms = my_get_time_ms;
platform.user_data = NULL;
/* 创建实例(NULL 使用默认配置) */
atcp_instance_t *inst = atcp_create(NULL, &platform);
/* 发起连接(主动端) */
atcp_connect(inst);
/* 主循环 */
while (1) {
atcp_tick(inst); /* 驱动协议栈,建议 10-15ms 调用一次 */
if (atcp_get_state(inst) == ATCP_STATE_CONNECTED) {
/* 发送数据 */
const uint8_t msg[] = "Hello MCU!";
atcp_send(inst, msg, sizeof(msg));
/* 接收数据 */
uint8_t buf[256];
size_t received = 0;
if (atcp_recv(inst, buf, sizeof(buf), &received) == ATCP_OK) {
/* 处理收到的数据 */
}
}
}
atcp_destroy(inst);
return 0;
}被动端使用 atcp_accept() 替代 atcp_connect():
atcp_instance_t *inst = atcp_create(NULL, &platform);
atcp_accept(inst); /* 监听握手请求 */
while (1) {
atcp_tick(inst);
/* ... 与主动端相同的收发逻辑 ... */
}| 函数 | 说明 |
|---|---|
atcp_create(config, platform) |
创建实例。config 传 NULL 使用默认值,platform 必须非 NULL |
atcp_destroy(inst) |
销毁实例,释放所有资源 |
| 函数 | 说明 |
|---|---|
atcp_connect(inst) |
发起连接(主动端),非阻塞 |
atcp_accept(inst) |
等待连接(被动端),非阻塞 |
atcp_disconnect(inst) |
断开连接,重置所有内部状态 |
| 函数 | 说明 |
|---|---|
atcp_send(inst, data, len) |
发送数据,非阻塞,数据入队后由 atcp_tick() 逐步发送 |
atcp_recv(inst, buf, buf_len, &received) |
从接收缓冲区读取已解码数据 |
注意事项:
atcp_send()单次最大发送 4096 字节- 上次
atcp_send()未完成时再次调用会返回ATCP_ERR_BUSY atcp_recv()无可读数据时返回ATCP_ERR_BUFFER_EMPTY
| 函数 | 说明 |
|---|---|
atcp_tick(inst) |
驱动协议栈的核心函数,处理音频 I/O、帧同步、解调、ACK/重传、心跳 |
atcp_tick() 建议以 10~15ms 周期调用,与 OFDM 符号时长(~12.3ms @ 44.1kHz)匹配。
| 函数 | 说明 |
|---|---|
atcp_get_state(inst) |
获取当前连接状态 |
atcp_get_stats(inst) |
获取统计信息(BER、SNR、吞吐量、帧计数等) |
atcp_get_audio_buf_size(inst) |
获取音频缓冲区所需大小(float 个数),平台层需保证缓冲区不小于此值 |
通过 atcp_config_t 结构体自定义协议行为:
atcp_config_t cfg = atcp_config_default(); /* 获取默认配置 */
cfg.qam_order = 64; /* 改为 64-QAM */
cfg.rs_nsym = 32; /* 减少 RS 冗余,提高速率 */
atcp_instance_t *inst = atcp_create(&cfg, &platform);| 参数 | 默认值 | 说明 |
|---|---|---|
n_fft |
512 | FFT 点数 |
cp_len |
32 | 循环前缀长度(采样数) |
sub_low |
2 | 最低可用子载波索引 |
sub_high |
200 | 最高可用子载波索引 |
n_train |
2 | 训练符号数 |
train_seed |
42 | 训练序列 PRNG 种子 |
lead_in |
1024 | 前导静音(采样数),约 23ms @ 44.1kHz |
lead_out |
1024 | 尾导静音(采样数) |
amplitude |
0.85 | 发射幅度上限 |
rs_nsym |
48 | RS 冗余符号数(纠错能力 = nsym/2 字节/块) |
qam_order |
16 | QAM 阶数(4/16/64/256) |
sample_rate |
44100 | 采样率(Hz) |
window_size |
4 | ARQ 滑动窗口大小 |
pilot_interval |
10 | 导频插入间隔 |
ack_timeout_ms |
500 | ACK 超时重传门限(ms) |
max_ack_miss |
3 | 连续 ACK 超时达此值后缩小窗口 |
ack_repeat |
2 | ACK 帧重复发送次数 |
heartbeatcp_interval_ms |
3000 | 心跳发送间隔(ms) |
heartbeatcp_timeout_ms |
10000 | 心跳超时断开门限(ms) |
| QAM | bits/符号 | 所需 SNR | 适用场景 |
|---|---|---|---|
| 4 (QPSK) | 2 | ~10 dB | 恶劣信道、握手、控制命令 |
| 16 | 4 | ~17 dB | 默认,平衡速率与可靠性 |
| 64 | 6 | ~23 dB | 良好信道,文件传输 |
| 256 | 8 | ~30 dB | 优质短线缆,最大吞吐 |
使用 ATCP 必须实现 atcp_platform_t 中的三个回调:
int (*audio_write)(const float *samples, int n_samples, int n_channels, void *user_data);将音频采样写入输出设备(Speaker / DAC)。
samples:交错格式浮点数据(双声道时为[L0, R0, L1, R1, ...])n_samples:每通道采样数n_channels:通道数,下行固定为 2(差分双声道)- 返回值:实际写入的每通道采样数,
< 0表示错误
int (*audio_read)(float *samples, int n_samples, int n_channels, void *user_data);从输入设备(Mic / ADC)读取音频采样。
n_samples:请求的每通道采样数n_channels:通道数- 返回值:实际读取的每通道采样数,
< 0表示错误
uint32_t (*get_time_ms)(void *user_data);返回当前系统时间(毫秒),用于心跳检测和 ARQ 超时计算。
平台实现示例:
| 平台 | audio_write / audio_read | get_time_ms |
|---|---|---|
| Windows | WASAPI / PortAudio | GetTickCount() |
| Linux | ALSA / PulseAudio | clock_gettime() |
| STM32 | DMA + DAC/ADC | HAL_GetTick() |
| ESP32 | I2S Driver | esp_timer_get_time() / 1000 |
| 状态码 | 值 | 说明 |
|---|---|---|
ATCP_OK |
0 | 成功 |
ATCP_ERR_INVALID_PARAM |
-1 | 参数无效 |
ATCP_ERR_NO_MEMORY |
-2 | 内存分配失败 |
ATCP_ERR_TIMEOUT |
-3 | 操作超时 |
ATCP_ERR_CRC_FAIL |
-4 | CRC 校验失败 |
ATCP_ERR_RS_DECODE_FAIL |
-5 | RS 解码失败(错误超过纠错能力) |
ATCP_ERR_SYNC_FAIL |
-6 | 帧同步失败 |
ATCP_ERR_HANDSHAKE_FAIL |
-7 | 握手失败 |
ATCP_ERR_DISCONNECTED |
-8 | 连接已断开 |
ATCP_ERR_BUSY |
-9 | 忙(上次操作未完成) |
ATCP_ERR_BUFFER_FULL |
-10 | 发送缓冲区满 |
ATCP_ERR_BUFFER_EMPTY |
-11 | 接收缓冲区空 |
ATCP_ERR_NOT_CONNECTED |
-12 | 未连接状态下调用收发 |
ATCP_ERR_QUALITY_LOW |
-13 | 链路质量过低 |
atcp_create()
│
▼
┌──────┐ atcp_connect() ┌────────────┐
│ IDLE │───────────────►│ CONNECTING │
│ │ atcp_accept() │ (握手中) │
└──────┘ └─────┬──────┘
│ 握手成功
▼
┌────────────┐
│ CONNECTED │◄──── 正常通信
└─────┬──────┘
│ 超时/主动断开
▼
┌───────────────┐
│ DISCONNECTED │
└───────────────┘
除了统一的高层 API,ATCP 也暴露了各协议层的接口,供高级用户按需组合使用。
通过包含对应的层级头文件即可访问:
#include <atcp/coding.h> /* RS 编解码、CRC32 */
#include <atcp/modulation.h> /* QAM、OFDM、帧同步、信道估计 */
#include <atcp/physical.h> /* 差分编解码、AGC、CFO/SFO */
#include <atcp/link.h> /* 帧结构、握手、ARQ、ACK、心跳 */示例 —— 单独使用 RS 编解码:
#include <atcp/coding.h>
atcp_rs_t rs;
atcp_rs_init(&rs, 48); /* 48 冗余符号,纠错 24 字节/块 */
/* 编码 */
uint8_t coded[4096];
int coded_len = 0;
atcp_rs_encode_blocks(&rs, data, data_len, coded, &coded_len);
/* 解码(自动纠错) */
uint8_t decoded[4096];
int decoded_len = 0;
atcp_rs_decode_blocks(&rs, coded, coded_len, decoded, &decoded_len);示例 —— 单独使用 QAM + OFDM:
#include <atcp/modulation.h>
#include <atcp/config.h>
atcp_config_t cfg = atcp_config_default();
/* QAM 调制 */
atcp_complex_t symbols[256];
int n_sym = 0;
atcp_qam_modulate(bits, n_bits, 16, symbols, &n_sym);
/* OFDM 调制 */
float time_samples[1024];
int time_len = 0;
atcp_ofdm_modulate(symbols, n_subs, &cfg, time_samples, &time_len);以默认参数(44.1kHz 采样率,N_FFT=512)估算:
| 模式 | QAM | 净吞吐量 |
|---|---|---|
| 独立帧 | QPSK | ~13 kbps |
| 独立帧 | 16-QAM | ~26 kbps |
| 连续流 | 16-QAM | ~47 kbps |
| 连续流 | 64-QAM | ~71 kbps |
| 连续流 | 256-QAM | ~95 kbps |
典型传输时间(连续流 + 16-QAM):
| 数据量 | 时间 |
|---|---|
| 1 KB | ~0.2s |
| 10 KB | ~1.7s |
| 100 KB | ~17s |
| 1 MB | ~3 min |
Q: 支持哪些平台? A: 任何支持 C99 编译器的平台。已在 Windows (MSVC)、Linux (GCC)、STM32 (ARM GCC)、ESP32 (Xtensa GCC) 上验证构建。
Q: 是否需要外部依赖?
A: 不需要。ATCP 零外部依赖,仅使用 C 标准库和 <math.h>。Unix/Linux 需链接 -lm。
Q: 如何提高传输速率?
A: 提高 QAM 阶数(需信道 SNR 支持)、提高采样率(需双方声卡支持)、或减少 RS 冗余(rs_nsym,会降低纠错能力)。
Q: 握手阶段为什么总是用 QPSK? A: 握手阶段尚未协商信道参数,使用最低阶调制(QPSK)确保在任何信道条件下都能成功建立连接。
Q: atcp_tick() 调用频率不规律会怎样?
A: 可能导致音频缓冲区溢出/欠载。建议使用定时器确保 10~15ms 的稳定调用周期。
Q: 如何处理连接断开?
A: 监测 atcp_get_state() 返回 ATCP_STATE_DISCONNECTED,然后重新调用 atcp_connect() / atcp_accept() 建立连接。