Arduino-PC串口开关控制视频闪烁问题及优化方案问询
开关控制视频叠加播放的抖动问题与优化方案
需求与问题
将通断开关连接至Arduino(引脚2接GND),通过9600波特率串口与PC通信:
- 开关断开:PC仅播放第一个预存视频
- 开关按下:在第一个视频上方叠加播放第二个视频
按下开关时第二个视频频繁闪烁,说明串口信号在0/1间波动;用指数衰减滑动平均滤波改善后,松开开关时停止叠加会有几秒延迟,寻求更优解决办法。
现有代码
Arduino代码(开关接GND与引脚2)
/* Input Pull-up Serial This example demonstrates the use of pinMode(INPUT_PULLUP). It reads a digital input on pin 2 and prints the results to the Serial Monitor. The circuit: - momentary switch attached from pin 2 to ground - built-in LED on pin 13 Unlike pinMode(INPUT), there is no pull-down resistor necessary. An internal 20K-ohm resistor is pulled to 5V. This configuration causes the input to read HIGH when the switch is open, and LOW when it is closed. This example code is in the public domain. */ void setup() { //启动串口连接 Serial.begin(9600); //配置引脚2为输入并启用内部上拉电阻 pinMode(2, INPUT_PULLUP); pinMode(13, OUTPUT); } void loop() { //读取按键值 int sensorVal = digitalRead(2); //打印按键值到串口 Serial.println(sensorVal); //上拉电阻导致逻辑反转:开关断开为HIGH,按下为LOW if (sensorVal == HIGH) { digitalWrite(13, LOW); } else { digitalWrite(13, HIGH); } }
Processing代码(读取串口并控制视频)
import processing.video.*; import processing.serial.*; Serial myPort; // 串口对象 boolean PlaySecondMovie = false; final int MovingAvgLength = 50; float MovingAvg = 0; final float MovingAvgThreshold = 0.002; final int NumMovies = 2; Movie mymovies[] = new Movie[NumMovies]; void setup() { // 列出所有可用串口 printArray(Serial.list()); // 打开第一个串口,波特率9600 myPort = new Serial(this, Serial.list()[0], 9600); size(640, 480); mymovies[0] = new Movie(this, "/Users/macbookpro/Desktop/Shiraz/2 films/2.mp4"); mymovies[0].loop(); mymovies[1] = new Movie(this, "/Users/macbookpro/Desktop/Shiraz/2 films/1.mp4"); mymovies[1].loop(); } float approxMovingAverage (float avg, float new_sample, int N) { avg -= avg / N; avg += new_sample / N; return avg; } // 读取视频新帧 void movieEvent(Movie mymovies) { mymovies.read(); } // 绘制画面 void draw() { while (myPort.available() > 0) { String inBuffer = myPort.readString(); if (inBuffer != null) { inBuffer = trim(inBuffer); println(inBuffer); if ("0".equals(inBuffer)) { MovingAvg = approxMovingAverage (MovingAvg, 1, MovingAvgLength); } else { MovingAvg = approxMovingAverage (MovingAvg, 0, MovingAvgLength); } println(MovingAvg); } } if (MovingAvg > MovingAvgThreshold) PlaySecondMovie = true; else PlaySecondMovie = false; background(0); image(mymovies[0], 0, 0, width, height); if (PlaySecondMovie) { image(mymovies[1], width/2, 0, width/2, height/2); mymovies[1].loop(); } else { mymovies[1].pause(); } }
优化方案
1. 硬件消抖(最直接)
在开关两端并联100nF电容+10kΩ电阻的RC电路,吸收机械开关按下/松开时的脉冲信号,从根源消除抖动。
2. Arduino端软件消抖+状态变化时才发送数据
修改Arduino代码,使用非阻塞式消抖(避免delay()),仅当开关状态发生变化时才向串口发送数据,减少无效数据传输:
void setup() { Serial.begin(9600); pinMode(2, INPUT_PULLUP); pinMode(13, OUTPUT); } const unsigned long debounceDelay = 50; // 消抖延时50ms int lastSwitchState = HIGH; // 上一次开关状态 int currentSwitchState; unsigned long lastDebounceTime = 0; void loop() { int reading = digitalRead(2); // 如果读取到的状态和上次不同,重置消抖计时 if (reading != lastSwitchState) { lastDebounceTime = millis(); } // 超过消抖延时后确认状态稳定 if ((millis() - lastDebounceTime) > debounceDelay) { currentSwitchState = reading; // 仅当状态变化时发送串口数据 if (currentSwitchState != lastSwitchState) { Serial.println(currentSwitchState); lastSwitchState = currentSwitchState; // 同步LED状态 digitalWrite(13, currentSwitchState == LOW ? HIGH : LOW); } } }
3. Processing端简化逻辑
因为Arduino已经处理了消抖,Processing不需要再做滑动平均,直接根据最新的串口指令切换状态,消除延迟:
import processing.video.*; import processing.serial.*; Serial myPort; boolean PlaySecondMovie = false; final int NumMovies = 2; Movie mymovies[] = new Movie[NumMovies]; void setup() { printArray(Serial.list()); myPort = new Serial(this, Serial.list()[0], 9600); myPort.bufferUntil('\n'); // 按换行符分割数据 size(640, 480); mymovies[0] = new Movie(this, "/Users/macbookpro/Desktop/Shiraz/2 films/2.mp4"); mymovies[0].loop(); mymovies[1] = new Movie(this, "/Users/macbookpro/Desktop/Shiraz/2 films/1.mp4"); mymovies[1].pause(); // 初始暂停第二个视频 } // 读取视频新帧 void movieEvent(Movie mymovies) { mymovies.read(); } // 串口数据事件(仅当收到完整行时触发) void serialEvent(Serial myPort) { String inBuffer = myPort.readStringUntil('\n'); if (inBuffer != null) { inBuffer = trim(inBuffer); // 上拉逻辑:0表示开关按下,1表示断开 PlaySecondMovie = "0".equals(inBuffer); } } // 绘制画面 void draw() { background(0); image(mymovies[0], 0, 0, width, height); if (PlaySecondMovie) { image(mymovies[1], width/2, 0, width/2, height/2); if (!mymovies[1].isPlaying()) { mymovies[1].loop(); } } else { if (mymovies[1].isPlaying()) { mymovies[1].pause(); } } }
优化说明
- 硬件消抖适合彻底解决机械抖动问题,不需要修改代码
- 软件消抖+状态变化发送数据,减少串口数据量,避免Processing收到大量波动信号
- Processing端用
bufferUntil('\n')和serialEvent处理串口数据,逻辑更简洁,消除滑动平均带来的延迟
内容的提问来源于stack exchange,提问作者Arma Yari
相关产品推荐
相关产品推荐

