Я использую ПК с Windows 7 для вывода напряжения с частотой 1 кГц. Сначала я просто завершил поток с помощью sleep_until (nextStartTime), однако это оказалось ненадежным, иногда работает нормально, а иногда и до 10 мс.
Здесь я нашел и другие ответы о том, что занятый цикл может быть более точным, однако мой по некоторым причинам также иногда занимает слишком много времени.
while (true) {
doStuff(); //is quick enough
logDelays();
nextStartTime = chrono::high_resolution_clock::now() + chrono::milliseconds(1);
spinStart = chrono::high_resolution_clock::now();
while (chrono::duration_cast<chrono::microseconds>(nextStartTime -
chrono::high_resolution_clock::now()).count() > 200) {
spinCount++; //a volatile int
}
int spintime = chrono::duration_cast<chrono::microseconds>
(chrono::high_resolution_clock::now() - spinStart).count();
cout << "Spin Time micros :" << spintime << endl;
if (spinCount > 100000000) {
cout << "reset spincount" << endl;
spinCount = 0;
}
}
Я надеялся, что это сработает, чтобы исправить мою проблему, однако он выдаст результат:
Spin Time micros :9999
Spin Time micros :9999
...
Я застрял в этой проблеме в течение последних 5 часов, и я был бы очень благодарен, если кто-нибудь знает решение.
Согласно комментариям этот код ожидает корректно:
auto start = std::chrono::high_resolution_clock::now();
const auto delay = std::chrono::milliseconds(1);
while (true) {
doStuff(); //is quick enough
logDelays();
auto spinStart = std::chrono::high_resolution_clock::now();
while (start > std::chrono::high_resolution_clock::now() + delay) {}
int spintime = std::chrono::duration_cast<std::chrono::microseconds>
(std::chrono::high_resolution_clock::now() - spinStart).count();
std::cout << "Spin Time micros :" << spintime << std::endl;
start += delay;
}
Важная часть занята ожидания while (start > std::chrono::high_resolution_clock::now() + delay) {}
а также start += delay;
который в сочетании убедится, что delay
время ожидания, даже когда это мешают внешние факторы (обновление Windows, сохраняющее занятость системы). В случае, если цикл занимает больше времени, чем delay
цикл будет выполнен без ожидания, пока он не догонит (что может быть никогда, если doStuff
достаточно медленно).
Обратите внимание, что пропущенное обновление (из-за загруженности системы) и последующая отправка сразу 2, чтобы наверстать упущенное, может быть не лучшим способом справиться с ситуацией. Вы можете проверить текущее время внутри doStuff
и прервите / перезапустите передачу, если время выбрано неверно более чем на приемлемую величину.
В Windows я не думаю, что возможно когда-либо получить такой точный тайминг, потому что вы не можете гарантировать, что ваш поток действительно работает в то время, которое вы хотите. Даже при низкой загрузке ЦП и настройке вашего потока на приоритет в реальном времени, он все равно может быть прерван (аппаратные прерывания, как я понимаю. Никогда полностью не исследовать, но даже просто while(true) ++i;
Цикл типа в реальном времени, который я видел, прерывался, а затем перемещался между ядрами процессора. Хотя такие прерывания и переключение для потока в реальном времени очень быстрые, это все равно важно, если вы пытаетесь направить сигнал напрямую без буферизации.
Вместо этого вы действительно хотите читать и записывать буферы цифровых сэмплов (поэтому при частоте 1 кГц каждый сэмпл равен 1 мс). Вы должны быть уверены, что поставили в очередь другой буфер до того, как последний будет завершен, что ограничит их размер, но при частоте 1 кГц с приоритетом в реальном времени, если код прост и нет других конфликтов ЦП, один буфер выборки (1 мс) может даже возможно, что в худшем случае 1 мс дополнительной задержки по сравнению с «немедленным», но вам придется проверить. Затем вы оставляете это на усмотрение аппаратного обеспечения и его драйверов для точной синхронизации (например, убедитесь, что каждая выходная выборка «точно» 1 мс с точностью, заявленной поставщиком).
По сути, это означает, что ваш код должен быть точным с точностью до 1 мс в худшем случае, вместо того, чтобы пытаться добиться чего-то гораздо меньшего, чем реально поддерживает ОС, например микросекундной точности.
До тех пор, пока вы сможете поставить в очередь новый буфер до того, как оборудование израсходует предыдущий буфер, он сможет без проблем работать на желаемой частоте (снова использовать звук в качестве примера, в то время как допустимые задержки часто намного выше и таким образом, буферы также, если вы перегружаете ЦП, вы все равно иногда можете услышать случайные сбои, когда приложение не помещало в очередь новый необработанный звук во времени).
При аккуратном выборе времени вы даже сможете сократить время до доли миллисекунды, ожидая обработки и помещения в очередь вашего следующего сэмпла как можно дольше (например, если вам нужно уменьшить задержку между входом и выходом), но помните, что чем ближе вы сократить его, тем больше вы рискуете представить его слишком поздно.