想法:我和一个朋友正在用(Elegoo)Arduino-Mega构建节拍器,另外,它还可以通过串行端口发送MIDI-Clock信号。与设置的BPM值同步闪烁LED,通过旋转编码器进行BPM控制,其他一切都很好。仅通过串行发送MIDI信号会让我们头疼。
问题:每个节拍,MIDI时钟信号(0xF8)需要发送24次。因此,我们只需计算时钟滴答之间的时间,然后经过时间间隔,就可以通过串行发送0xF8。简单。但是,当我们将其连接到Ditto X4吉他弯管器时,节拍器的LED闪烁和弯管器的LED闪烁不同步。因此,我们在C#.NET中编写了一个小脚本,以验证通过串行发送的内容,结果表明,根据设置的BPM,某些消息根本没有发送或被延迟,这导致循环程序计算出的BPM与我们尝试发送(Screenshot of script output)。
但是我们在这里完全迷路了。为什么有些消息被延迟/未发送?即使在“正常”波特率(如9600)上,问题也相同。而且它似乎与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)));
}
}
脚本计算相对于实际发送前一条消息的时间而言,下一条消息的时间。这意味着所有延迟都会加起来。
相反,应该从发送最后一条消息开始,以固定间隔计算下一次:
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
}
}
为什么某些消息被延迟/未发送?即使在“正常”波特率(如9600)上,问题也相同。而且它似乎不随Arduino CPU的使用而扩展或设置BPM
这似乎与您测量消息之间的时间有关。
C#代码使用具有以下注释的Stopwatch
类:
在多处理器计算机上,线程在哪个处理器上运行无关紧要。但是,由于BIOS或硬件抽象层(HAL)中的错误,您可以在不同的处理器上获得不同的计时结果。要指定线程的处理器相似性,请使用ProcessThread.ProcessorAffinity方法。
我的重点从https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.stopwatch?view=netcore-3.1中添加
因此,当控制台应用程序在执行过程中切换处理器内核时,您可能会获得不同的计时结果。
通过使用以下命令来设置控制台应用程序的运行时,可以通过设置处理器关联来避免此问题:
ProcessThread.ProcessorAffinity
START
和/affinity
flag失败,测量消息之间的时间间隔的另一种方法是CRO / DSO,甚至使用一个Arduino测量另一个消息的输出。
但是,当我们将它连接到Ditto X4吉他弯管器时,节拍器的LED闪烁和弯管器的LED闪烁不同步。
对于您的实际应用,不清楚这个问题有多快,LED和Looper不同步多少。
有不同程度的几个问题需要考虑:
micros()
分辨率为4 µs。由于整数舍入问题和轮询延迟,每拍节来自Arduino的消息可能会耗费4-8 µs。 这些差异在总体方案中很小,但更重要的是,[[无关如果Arduino是发出时钟信号的“导体”。我的猜测是所连接的MIDI设备上的LED不是从Arduino的MIDI时钟信号得出其频率。