Arduino 串行写入 skips/delays 消息

Arduino Serial write skips/delays Messages

想法: 我和一个朋友正在用 (Elegoo) Arduino-Mega 构建节拍器,它还可以通过串行端口发送 MIDI 时钟信号。 与设置的 BPM 值同步闪烁 LED,通过旋转编码器进行 BPM 控制,其他一切工作正常。仅通过串行发送 MIDI 信号让我们头疼。

问题: MIDI 时钟信号 (0xF8) 需要在每个节拍上发送 24 次。因此,我们简单地计算了时钟滴答之间的时间,在时间间隔过去后,我们通过串行发送一个 0xF8。简单的。 但是,当我们将它连接到 Ditto X4 Guitar Looper 时,节拍器的 LED 闪烁和 Looper 的 LED 不同步。因此,我们在 C# .NET 中编写了一个小脚本来验证通过 Serial 发送的内容,结果表明,根据设置的 BPM,一些消息根本没有发送或被延迟,这导致循环器计算与不同的 BPM我们尝试发送(Screenshot of script output).

但是我们在这里完全迷路了。为什么发送了一些消息 delayed/not?即使在像 9600 这样的 "normal" 波特率下,问题也是一样的。而且它似乎不随 Arduino CPU 使用或设置 BPM:

扩展
Set BPM:      Lost Message every x Messages:
  300                      24-26
  150                      10-12
  50                       4-5

我们还测试了 Arduino Uno R3(也来自 Elegoo),但问题是一样的。

此示例脚本可用于复制问题:

#include <Arduino.h> //Einbinden der Arduino Bibliothek

//Timer Variables
unsigned long startTimeMIDI = 0;
unsigned long currentTime = 0;
unsigned long intervalLED;
unsigned long intervalMIDI;

short counter_BPM = 300 * 2; // Internally we use BMP*2

void setup()
{
  Serial.begin(31250); //Forced by the MIDI standard
  while ( !Serial ) /*wait for serial init*/ ;
}

void loop()
{
  currentTime = micros();

  intervalLED = (120000000/counter_BPM); //60000000*(BPM/2)
  intervalMIDI = intervalLED/24; //Midi Clock has to be sent 24 times for each beat

  if (currentTime - startTimeMIDI > intervalMIDI){
    Serial.write(0xF8);    //send MIDI Clock signal
    startTimeMIDI = currentTime;  //reset timer value
  }
}

这是用于监控发送内容的 C# 脚本:

static void Main(string[] args)
    {
        serial = new SerialPort("COM4", 31250);
        serial.Open();

        int cycleSize = 50; //Averaging over 50 Values

        long[] latencyList = new long[cycleSize+1];

        Stopwatch watch = new Stopwatch();
        watch.Start();

        int n = 0;
        while(true)
        {
            n++;
            watch.Start();

            int response = serial.ReadByte();
            watch.Stop();

            long latency = watch.ElapsedTicks/(Stopwatch.Frequency/(1000L*1000L));
            watch.Reset();

            if (n <= cycleSize)
            {
                latencyList[n] = latency;
            }
            else
            {
                latencyList[n % cycleSize] = latency;
            }

            double average = latencyList.Average();
            Console.WriteLine("" + n + " " + latency.ToString("000000") + "µs - response:" + response + " - Average: " + average.ToString("##0.00") + " - BPM: " + (int)(60000000/(average * 24)));

        }
}

编辑:(2020 年 5 月 9 日) 我需要澄清一下吉他 Looper 的问题: 由于循环器用于将其效果同步到乐队的其余部分,因此这是最重要的问题。 Arduinos BPM LED 的闪烁(我们 tapped 它在各种设定的 BPM 下足够接近,足以认为它足够准确)和 Loopers LED 的闪烁相互偏离太快以至于无法接受。我们将 LED 彼此并排放置,它们会在大约 30 秒内从同步变为交替闪烁,因此在现场音乐会中一切都会崩溃。 由于 Looper LED 闪烁是由它接收到的 MIDI 输入触发的,我们查看了发送的时钟信号的一致性,发现信号之间存在奇怪的延迟。

脚本计算下一条消息相对于上一条消息实际发送时间的时间。这意味着任何延迟都会增加。

相反,将下一次计算为从最后一条消息应该发送的固定时间间隔:

void setup()
{
  Serial.begin(31250); //Forced by the MIDI standard
  while ( !Serial ) /*wait for serial init*/ ;

  intervalLED = (120000000/counter_BPM); //60000000*(BPM/2)
  intervalMIDI = intervalLED/24; //Midi Clock has to be sent 24 times for each beat

  startTimeMIDI = micros() + intervalMIDI;
}

void loop()
{
  if (micros() >= startTimeMIDI) {
    Serial.write(0xF8);             //send MIDI Clock signal
    startTimeMIDI += intervalMIDI;  //next timer value
  }
}

Why are some Messages delayed/not sent? Even on "normal" Baud rates like 9600 the Problem is the same. And it doesn't seem to scale with Arduino CPU usage or set BPM

这似乎与您如何测量消息之间的时间有关。

C# 代码使用 Stopwatch class 有这个注释:

On a multiprocessor computer, it does not matter which processor the thread runs on. However, because of bugs in the BIOS or the Hardware Abstraction Layer (HAL), you can get different timing results on different processors. To specify processor affinity for a thread, use the ProcessThread.ProcessorAffinity method.

我的强调来自 https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.stopwatch?view=netcore-3.1

因此,当控制台应用程序在执行期间切换处理器内核时,您可能会得到不同的计时结果。

您可以通过在控制台应用程序运行时设置处理器关联来避免此问题:

  • ProcessThread.ProcessorAffinity 代码
  • 使用 START/affinity flag
  • Task Manager

如果做不到这一点,另一种测量消息间隔的方法是 CRO/DSO 或者甚至使用一个 Arduino 来测量另一个的输出。

But when we hooked it up to a Ditto X4 Guitar Looper the LED blinking of our metronome and that of the looper went out of sync.

至于您的实际应用,从问题中不清楚 LED 和循环器不同步的速度和程度。

有几个问题需要不同程度的考虑:

  • micros() 分辨率为 4 微秒。由于整数舍入问题和轮询延迟,来自 Arduino 的消息可能每拍 4-8 微秒。
  • Arduino 时钟频率精度。从 Elegoo 网站上的图片来看,他们的电路板似乎使用陶瓷谐振器,其稳定性和精度不如晶体。

这些差异在事物的宏伟计划中很小,但更重要的是 无关紧要 如果 Arduino 是 "conductor" 发出时钟信号。我的猜测是连接的 MIDI 设备上的 LED 没有从 Arduino 的 MIDI 时钟信号中获取其频率。

时隔一年多,我终于来回答我自己的问题了。 与此同时,我有可能使用示波器来分析时序。 事实证明,LED 和串行端口的输出明显分开。 我不知道为什么,据我所知,Arduino 的串行输出与 cpu 时钟异步处理,但我没想到会有这么大的漂移。

特别是针对我们的问题的非常简单的解决方案是简单地从本机串行端口切换到软件串行端口。这样 cpu 不仅可以处理闪烁,还可以处理串行通信,因此在示波器上看不到漂移。

至于有关 C# 脚本的评论和调试,有些人已经说对了,如此准确的计时无法通过如此简单的方法可靠地工作。 arduino 确实没有丢失消息,但“只是”漂移得很厉害。