提示:本文内容仅供学习参考。Author: Jonnie Walker CGC
目录
[(3).E SP32-C3连接电路](#(3).E SP32-C3连接电路)
前言
前一段时间在使用FS-i6X遥控器,测试过程将其记录下来,分享给需要的朋友。
这段时间在DIY仿生蝴蝶,使用到这个遥控器所以将其记录一下!
一、PPM是什么?
1.脉冲位置调制 (Pulse Position Modulation) 。
(1).核心概念 :在一个固定的时间周期内,通过改变脉冲出现的时间位置来携带和传递数据信息。
(2).常见应用:
-
无线电遥控 (RC):在无人机、航模或机器人的遥控器和接收机之间,PPM 是一种经典的模拟信号协议,可以用一根信号线同时传输多个通道的控制数据。
-
光通信:常用于深空通信或红外线遥控中。
二、测试步骤
1.硬件
(1).FS-A8S接收机:
图1 正 图2 反


(2).FS-i6X遥控器
在测试前先进行遥控器PPM设置:
图1 进入系统

图2 选择接收机设置

图3 进入后选择输出模式

图4 选择本次测试的PPM输出模式

图5 回到系统,然后选择辅助开关设置

图6 通道数设置为8,然后将所有开关打开

图7 然后退出系统选择进入功能

图8 选择辅助通道

图9 设置对应通道,不知道怎么设置,就按照图设置进行

图10

遥控器的设置到此就完成。
(3).E SP32-C3连接电路
图1

2.软件
以下为本次测试的程序1,8通道输出。阻塞版本:
cpp
/*
* PPM信号读取测试程序 - ESP32-版本
* 文件名:PPM_Reader_Test.ino
* 作者:iTEM-CGC
* 版本:1.0
* 日期:2025-12-1
*
* 功能描述:
* - 读取8通道PPM信号
* - 通过串口输出原始脉冲宽度(微秒)
* - 简单的数据格式输出
*
* 硬件连接:
* - PPM信号线 -> GPIO6
* - PPM GND -> ESP32 GND
* - PPM VCC -> ESP32 3.3V (如果需要)
*/
/*******************************
* 宏定义 *
*******************************/
#define PIN_PPM_INPUT 2 // PPM信号输入引脚 (GPIO2)
#define BAUD_RATE 115200 // 串口波特率
#define CHANNEL_COUNT 8 // 要读取的通道数量
// PPM信号参数
#define PPM_SYNC_THRESHOLD 5000 // 同步脉冲阈值(微秒)
#define PPM_MIN_PULSE 500 // 最小脉冲宽度(微秒)
#define PPM_MAX_PULSE 2500 // 最大脉冲宽度(微秒)
/*******************************
* 全局变量 *
*******************************/
int ppmChannels[CHANNEL_COUNT] = {0}; // 存储通道数据
unsigned long frameCount = 0; // 帧计数器
unsigned long lastFrameTime = 0; // 上一帧时间
float frameRate = 0.0; // 帧率
/*******************************
* 主程序 *
*******************************/
/**
* @brief 初始化
*/
void setup() {
// 初始化串口
Serial.begin(BAUD_RATE);
while (!Serial) {
; // 等待串口连接
}
// 初始化PPM输入引脚
pinMode(PIN_PPM_INPUT, INPUT);
Serial.println("========================================");
Serial.println("PPM信号读取测试程序");
Serial.println("========================================");
Serial.println("等待PPM信号...");
Serial.println();
}
/**
* @brief 主循环
*/
void loop() {
// 读取PPM数据
bool success = readPPMChannels();
// 帧率统计
frameCount++;
unsigned long currentTime = millis();
if (currentTime - lastFrameTime >= 1000) {
frameRate = frameCount * 1000.0 / (currentTime - lastFrameTime);
frameCount = 0;
lastFrameTime = currentTime;
}
// 如果成功读取到数据,则输出
if (success) {
printPPMData();
}
}
/**
* @brief 读取PPM通道数据
* @return true: 读取成功, false: 读取失败
*/
bool readPPMChannels() {
// 等待同步脉冲(长脉冲)
unsigned long syncStart = micros();
while (pulseIn(PIN_PPM_INPUT, HIGH) < PPM_SYNC_THRESHOLD) {
// 超时检测(100ms超时)
if (micros() - syncStart > 100000) {
return false;
}
}
// 读取所有通道数据
for (int i = 0; i < CHANNEL_COUNT; i++) {
unsigned long pulseStart = micros();
ppmChannels[i] = pulseIn(PIN_PPM_INPUT, HIGH);
// 超时检测(5ms超时)
if (micros() - pulseStart > 5000) {
ppmChannels[i] = 0;
return false;
}
}
return true;
}
/**
* @brief 打印PPM数据
*/
void printPPMData() {
static unsigned long lastPrintTime = 0;
unsigned long currentTime = millis();
// 控制输出频率(每100ms输出一次)
if (currentTime - lastPrintTime < 100) {
return;
}
lastPrintTime = currentTime;
// 输出时间戳和帧率
Serial.print("[");
Serial.print(currentTime / 1000); // 转换为秒
Serial.print(".");
Serial.print(currentTime % 1000);
Serial.print("s] FR:");
Serial.print(frameRate, 1);
Serial.print("Hz | ");
// 输出所有通道数据
for (int i = 0; i < CHANNEL_COUNT; i++) {
Serial.print("CH");
if (i+1 < 10) Serial.print("0");
Serial.print(i+1);
Serial.print(":");
Serial.print(ppmChannels[i]);
Serial.print("us ");
}
// 输出通道状态指示
Serial.print("| [");
for (int i = 0; i < CHANNEL_COUNT; i++) {
if (ppmChannels[i] >= PPM_MIN_PULSE && ppmChannels[i] <= PPM_MAX_PULSE) {
Serial.print("✓");
} else if (ppmChannels[i] == 0) {
Serial.print("-");
} else {
Serial.print("✗");
}
}
Serial.println("]");
}
/**
* @brief 可选:简洁输出模式(每帧都输出)
*/
void printPPMDataSimple() {
// 简洁输出格式
Serial.print("[");
Serial.print(millis());
Serial.print("] ");
for (int i = 0; i < CHANNEL_COUNT; i++) {
Serial.print(ppmChannels[i]);
if (i < CHANNEL_COUNT - 1) {
Serial.print(",");
}
}
Serial.println();
}
/**
* @brief 可选:详细输出模式
*/
void printPPMDataDetailed() {
static unsigned long lastDetailedPrint = 0;
unsigned long currentTime = millis();
// 每500ms输出一次详细报告
if (currentTime - lastDetailedPrint < 500) {
return;
}
lastDetailedPrint = currentTime;
Serial.println("\n=== PPM数据详细报告 ===");
Serial.println("通道 原始值(us) 状态");
Serial.println("-----------------------");
for (int i = 0; i < CHANNEL_COUNT; i++) {
Serial.print("CH");
if (i+1 < 10) Serial.print("0");
Serial.print(i+1);
Serial.print(": ");
Serial.print(ppmChannels[i]);
Serial.print("us\t");
// 判断状态
if (ppmChannels[i] == 0) {
Serial.println("无信号");
} else if (ppmChannels[i] < PPM_MIN_PULSE) {
Serial.println("过低");
} else if (ppmChannels[i] > PPM_MAX_PULSE) {
Serial.println("过高");
} else if (abs(ppmChannels[i] - 1500) < 50) {
Serial.println("中位");
} else if (ppmChannels[i] > 1500) {
Serial.print("高位 +");
Serial.print(ppmChannels[i] - 1500);
Serial.println("us");
} else {
Serial.print("低位 ");
Serial.print(ppmChannels[i] - 1500);
Serial.println("us");
}
}
Serial.print("帧率: ");
Serial.print(frameRate, 1);
Serial.println(" Hz");
Serial.println("=======================\n");
}
以下为本次测试的程序2,8通道输出。外部中断版本:
cpp
/*
* PPM信号读取测试程序 - ESP32-版本
* 文件名:PPM_Reader_Test.ino
* 作者:iTEM-CGC
* 版本:1.0
* 日期:2025-12-1
*
* 功能描述:
* - 读取8通道PPM信号
* - 通过串口输出原始脉冲宽度(微秒)
* - 简单的数据格式输出
*
* 硬件连接:
* - PPM信号线 -> GPIO6
* - PPM GND -> ESP32 GND
* - PPM VCC -> ESP32 3.3V (如果需要)
*/
#define PPM_INPUT_PIN 2 // 定义PPM输入引脚
#define MAX_CHANNELS 8 // 定义最大通道数(通常航模遥控器为6-8通道)
#define SYNC_PULSE_MIN_LENGTH 3000// 同步脉冲的最小长度(微秒),通常大于3000us
// 声明 volatile 变量,因为它们将在中断服务程序(ISR)和主循环中共享
volatile uint16_t ppmValues[MAX_CHANNELS]; // 存放各个通道的值
volatile uint8_t currentChannel = 0; // 当前正在读取的通道索引
volatile uint32_t lastMicros = 0; // 上次触发中断的时间戳
volatile bool newFrameReady = false; // 标记是否成功接收到一帧完整的数据
// IRAM_ATTR 宏将该函数放入内部RAM中执行,这对于ESP32的高频中断非常重要
void IRAM_ATTR ppmInterrupt() {
uint32_t currentMicros = micros(); // 获取当前时间(微秒)
uint32_t delta = currentMicros - lastMicros; // 计算与上次中断的时间差
lastMicros = currentMicros; // 更新上次中断时间
// 判断是否为同步帧(长脉冲)
if (delta > SYNC_PULSE_MIN_LENGTH) {
currentChannel = 0; // 遇到同步帧,通道计数器清零,准备接收通道1
newFrameReady = true; // 标记上一帧数据已经完整接收
}
// 否则,如果通道数没有超过最大值,则记录通道数据
else if (currentChannel < MAX_CHANNELS) {
ppmValues[currentChannel] = delta;
currentChannel++;
}
}
void setup() {
Serial.begin(115200);
// 设置PPM引脚为输入模式。如果接收机信号比较弱,可以尝试使用 INPUT_PULLUP
pinMode(PPM_INPUT_PIN, INPUT);
// 将中断附加到引脚上,大多数PPM信号通过计算两个上升沿(RISING)之间的宽度来获取数值
attachInterrupt(digitalPinToInterrupt(PPM_INPUT_PIN), ppmInterrupt, RISING);
Serial.println("ESP32-C3 PPM 通信测试程序已启动...");
Serial.println("等待PPM信号...");
}
void loop() {
// 如果成功接收到一帧新的PPM数据
if (newFrameReady) {
// 临时数组,用于安全复制数据
uint16_t safePpmValues[MAX_CHANNELS];
// 禁用中断,防止在复制数据的过程中发生中断导致数据被覆盖(数据撕裂现象)
noInterrupts();
for (int i = 0; i < MAX_CHANNELS; i++) {
safePpmValues[i] = ppmValues[i];
}
newFrameReady = false; // 重置标志位
interrupts(); // 恢复中断
// 在串口监视器打印所有通道的值
Serial.print("PPM 帧数据: ");
for (int i = 0; i < MAX_CHANNELS; i++) {
Serial.print("CH");
Serial.print(i + 1);
Serial.print(": ");
// 输出数值,正常情况应在 1000 到 2000 之间
Serial.print(safePpmValues[i]);
Serial.print(" \t");
}
Serial.println();
// 延迟一小段时间,避免串口输出过快(一帧PPM通常大约20ms)
delay(20);
}
}
程序运行后打开串口工具,然后窗口会输出8个通道数据。在结合操作遥控器观察其数据的变化!
总结
本文详细描述比较少!希望能对你有帮助。
