#c #performance #32-bit #pic #16-bit
#c #Производительность #32-разрядный #рис #16-разрядный
Вопрос:
Я работаю над некоторой прошивкой для встроенного устройства, которое использует 16-битный PIC, работающий со скоростью 40 MIPS и программирующий на C. Система будет контролировать положение двух шаговых двигателей и постоянно поддерживать положение шага каждого двигателя. Максимальное положение каждого двигателя составляет около 125000 шагов, поэтому я не могу использовать 16-битное целое число для отслеживания положения. Я должен использовать 32-битное целое число без знака (DWORD). Двигатель движется со скоростью 1000 шагов в секунду, и я разработал прошивку таким образом, чтобы шаги обрабатывались по таймеру ISR. Таймер ISR выполняет следующее:
1) сравните текущее положение одного двигателя с целевым положением, если они совпадают, установите флаг isMoving false и верните. Если они разные, установите флаг isMoving true.
2) Если целевая позиция больше текущей позиции, переместитесь на один шаг вперед, затем увеличьте текущую позицию.
3) Если целевая позиция меньше текущей позиции, переместитесь на один шаг назад, затем уменьшите текущую позицию.
Вот код:
void _ISR _NOPSV _T4Interrupt(void)
{
static char StepperIndex1 = 'A';
if(Device1.statusStr.CurrentPosition == Device1.statusStr.TargetPosition)
{
Device1.statusStr.IsMoving = 0;
// Do Nothing
}
else if (Device1.statusStr.CurrentPosition > Device1.statusStr.TargetPosition)
{
switch (StepperIndex1) // MOVE OUT
{
case 'A':
SetMotor1PosB();
StepperIndex1 = 'B';
break;
case 'B':
SetMotor1PosC();
StepperIndex1 = 'C';
break;
case 'C':
SetMotor1PosD();
StepperIndex1 = 'D';
break;
case 'D':
default:
SetMotor1PosA();
StepperIndex1 = 'A';
break;
}
Device1.statusStr.CurrentPosition--;
Device1.statusStr.IsMoving = 1;
}
else
{
switch (StepperIndex1) // MOVE IN
{
case 'A':
SetMotor1PosD();
StepperIndex1 = 'D';
break;
case 'B':
SetMotor1PosA();
StepperIndex1 = 'A';
break;
case 'C':
SetMotor1PosB();
StepperIndex1 = 'B';
break;
case 'D':
default:
SetMotor1PosC();
StepperIndex1 = 'C';
break;
}
Device1.statusStr.CurrentPosition ;
Device1.statusStr.IsMoving = 1;
}
_T4IF = 0; // Clear the Timer 4 Interrupt Flag.
}
Целевая позиция устанавливается в основном цикле программы при получении запросов на перемещение. Строки SetMotorPos — это просто макросы для включения / выключения определенных контактов порта.
Мой вопрос: есть ли какой-либо способ повысить эффективность этого кода? Код функционирует нормально, как есть, если позиции являются 16-битными целыми числами, но при 32-битных целых числах требуется слишком много обработки. Это устройство должно без колебаний взаимодействовать с ПК, и, как написано, наблюдается заметное снижение производительности. Мне действительно нужна только 18-битная математика, но я не знаю простого способа сделать это! Любой конструктивный ввод / предложения будут высоко оценены.
Комментарии:
1. Устройство PIC24HJ128GP204 на случай, если кому-то интересно.
2. Что генерирует компилятор? Какие флаги оптимизации вы используете?
3. Пока вы только добавляете и вычитаете 32-битные значения, не умножая и не деля (особенно не разделяя), влияние на производительность должно быть очень незначительным. Ищите свои задержки в другом месте, они не вызваны 32-битным добавлением / приращениями.
4. @Ben Voigt: В чем будет разница в инструкциях по вычитанию 32-битных чисел по сравнению с 16-битными числами? Будет ли это в два раза больше?
5. Зависит от того, предлагает ли набор команд «добавить с переносом» и соответствующую инструкцию «вычесть с заимствованием» или нет. Если это произойдет, это просто добавить и добавить с переносом или подзаголовок и подзаголовок с заимствованием соответственно (2 инструкции). Если нет, заимствование должно быть смоделировано, что будет немного больше. Другое дело, что компилятор может выполнять ужасную работу. Даже в этом случае кажется более вероятным, что ваш блок переключения будет дороже, чем приращение / уменьшение.
Ответ №1:
Предупреждение: все числа составлены…
Предположим, что вышеупомянутый ISR содержит около 200 (вероятно, меньше) инструкций скомпилированного кода, и они включают инструкции по сохранению / восстановлению регистров процессора до и после ISR, каждая из которых занимает 5 тактов (вероятно, от 1 до 3), и вы вызываете 2 из них по 1000 раз в секунду каждый, мы получаем 2*1000*200*5 = 2 миллиона тактов в секунду или 2 MIPS.
Вы действительно используете остальные 38 MIPS в другом месте?
Единственное, что может быть важным здесь, и я этого не вижу, это то, что делается внутри функций SetMotor * Pos *(). Выполняют ли они какие-либо сложные вычисления? Выполняют ли они некоторую медленную связь с двигателями, например, ждут, пока они ответят на отправленные им команды?
В любом случае, сомнительно, что такой простой код будет заметно медленнее при работе с 32-битными целыми числами, чем с 16-битными.
Если ваш код медленный, выясните, на что тратится время и сколько, профилируйте его. Сгенерируйте прямоугольный импульсный сигнал в ISR (равный 1 при запуске ISR, равный 0, когда ISR собирается вернуться) и измерьте его длительность с помощью осциллографа. Или сделайте все, что проще, чтобы это выяснить. Измерьте время, затраченное на все части программы, затем оптимизируйте там, где это действительно необходимо, а не там, где вы ранее думали, что это будет.
Комментарии:
1. 1 за использование осциллографа в качестве профилировщика, напоминает мне старые добрые университетские вечерние взломы.
Ответ №2:
Я думаю, разница между 16 и 32-битной арифметикой не должна быть такой большой, поскольку вы используете только приращение и сравнение. Но, возможно, проблема в том, что каждая 32-битная арифметическая операция подразумевает вызов функции (если компилятор не может / не желает выполнять встраивание более простых операций).
Одним из предложений было бы выполнить арифметику самостоятельно, разбив Device1.statusStr.currentPosition на два, скажем, Device1.statusStr.CurrentPositionH и Device1.statusStr.CurrentPositionL. Затем используйте некоторые макросы для выполнения операций, таких как:
#define INC(xH,xL) {xL ;if (xL == 0) xH ;}
Комментарии:
1. Перечисление 32-разрядных дополнений на 16-разрядном оборудовании обходится дешево (и намного дешевле, чем переходы). Если вы беспокоитесь о производительности, то следует обратить внимание на умножения, деления и плавающую точку, поскольку их гораздо дороже реализовать в программном обеспечении.
2. Проблема, о которой я говорю, заключается не во времени для вычисления, а во времени для вызова функции. Я бы рекомендовал делать это, ТОЛЬКО если скомпилированный файл не будет выполнять встраивание.
3. Технически это зависит от компилятора и аппаратного обеспечения, но почти никто не стал бы добавлять 32-битное целое число на 16-битном оборудовании, выполняя вызов функции. На 16-разрядной x86 это было бы сделано путем ДОБАВЛЕНИЯ, за которым сразу же следует АЦП (добавление с переносом).
4. Обратите внимание, что вопрос касается встроенных устройств. В средах с ограниченной памятью чаще встречаются вызовы функций для математической библиотеки.
5. Это ничего не меняет. Вызов функции на 16-разрядных устройствах занимает не менее 3 байт для кодирования, тогда как 32-разрядное добавление может быть выполнено за 2 байта. Следовательно, вызов функции выполняется медленнее и больше.
Ответ №3:
Я бы избавился от StepperIndex1
переменной и вместо этого использовал два младших бита CurrentPosition
для отслеживания текущего индекса шага. Кроме того, следите за текущей позицией в полных оборотах (а не за каждым шагом), чтобы она могла вписываться в 16-битную переменную. При перемещении вы увеличиваете / уменьшаете позицию только при переходе к фазе «A». Конечно, это означает, что вы можете настроить таргетинг только на каждый полный оборот, а не на каждый шаг.
Ответ №4:
Извините, но вы используете плохой дизайн программы.
Давайте проверим разницу между 16-битным и 32-битным ASM-кодом PIC24 или PIC33…
увеличение на 16 бит
inc PosInt16 ;one cycle
Таким образом, увеличение на 16 бит занимает один цикл
увеличение на 32 бита
clr Wd ;one cycle
inc low PosInt32 ;one cycle
addc high PosInt32, Wd ;one cycle
и приращение 32 занимает три цикла.
Общая разница составляет 2 цикла или 50 нс (наносекунд).
Простое вычисление покажет вам все. У вас 1000 шагов в секунду и 40Mips DSP таким образом, у вас есть 40000 инструкций на шаг со скоростью 1000 шагов в секунду. Более чем достаточно!
Комментарии:
1. Теперь сравните скорость умножения.
Ответ №5:
Когда вы меняете его с 16-битного на 32-битный, вы меняете какой-либо из флагов компиляции, чтобы указать ему вместо этого компилироваться как 32-битное приложение.
вы пробовали компилировать с 32-битными расширениями, но используя только 16-битные целые числа. вы все еще получаете такое падение производительности?
Вполне вероятно, что просто при переходе с 16-битного на 32-битный некоторые операции компилируются по-разному, возможно, сделайте различие между двумя наборами скомпилированного ASM-кода и посмотрите, что на самом деле отличается, это много или всего пара строк.
Решения были бы, возможно, вместо использования 32-битного целого числа, просто используйте два 16-битных целых числа, когда значение равно int16.Max затем установите его в 0, а затем увеличьте ValueB на 1, в противном случае просто увеличьте ValueA на 1, когда значение B равно > = 3, затем вы проверяете ValueA > = 26696 (или что-то подобное, в зависимости от того, используете ли вы unsigned или signed int16), а затем ваш двигатель проверяет 12500.