Эффективное обратное (1 / х) для AVR


12

Я пытаюсь найти эффективный способ вычисления обратного на AVR (или аппроксимируя его).

Я пытаюсь рассчитать период импульса для шагового двигателя, чтобы я мог линейно изменять скорость. Период пропорционален обратной скорости ( p = K/v), но я не могу придумать хороший способ рассчитать это на лету.

Моя формула

p = 202/v + 298; // p in us; v varies from 1->100

При тестировании на Arduino разделение, похоже, полностью игнорируется, оставляя pфиксированным на 298(хотя, возможно, в avr-gcc это будет иначе). Я также пытался суммировать vв цикле, пока он не превысил 202, и подсчитывал циклы, но это довольно медленно.

Я мог бы создать таблицу поиска и сохранить ее во флэш-памяти, но мне было интересно, есть ли другой способ.

Редактировать : Может быть, заголовок должен быть "эффективный разрыв" ...

Обновление : как указывает Pingswept, моя формула для отображения периода на скорость неверна. Но главная проблема - операция деления.

Редактировать 2 : При дальнейшем исследовании, разделение работает на Arduino, проблема была связана с неправильной формулой выше и переполнением int в другом месте.


2
Является ли v целым или с плавающей точкой?
mjh2007

Целое число, но, поскольку оно дает нам период, целочисленное деление здесь достаточно точное.
Питер Гибсон

Вы можете предварительно вычислить значения 100 целых чисел и составить таблицу поиска предварительных скейлеров для умножения, если вы действительно обеспокоены скоростью. Конечно, есть обмен памяти.
RYS

Ответы:


7

Одна хорошая вещь о разделении - то, что все более или менее делают это. Это довольно характерная особенность языка Си, и компиляторы, такие как AVR-GCC (называемый IDE Arduino), выберут лучший алгоритм деления, даже если у микроконтроллера нет инструкции аппаратного деления.

Другими словами, вам не нужно беспокоиться о том, как реализовано деление, если у вас нет очень странного особого случая.


Если вы беспокоитесь, то вам может понравиться чтение официальных предложенных алгоритмов деления Atmel (один оптимизирован для размера кода, а другой оптимизирован для скорости выполнения; ни один не занимает память). Они в:

http://www.atmel.com/dyn/resources/prod_documents/doc0936.pdf

это примечание по применению «AVR200: процедуры умножения и деления», перечисленные на странице Atmel для его (достаточно больших) процессоров Atmega, таких как Atmega 168 и Atmega 328, используемых в стандартных Arduinos. Список технических паспортов и замечаний по применению находится по адресу:

http://www.atmel.com/dyn/products/product_card.asp?part_id=4720


4

Похоже, все, что вам нужно, это таблица поиска на 100 записей. Не становится намного быстрее, чем это.

#define VALUE_FOR_V_EQUALS_ZERO 0
uint16_t formula_lookup[100] = {VALUE_FOR_V_EQUALS_ZERO, 500, 399, 365, 348, ..., 300};

...

//"calculate" formula
p = formula_lookup[v > 67 ? 67 : v];

РЕДАКТИРОВАТЬ вы на самом деле только справочную таблицу с 68 значениями, поскольку значения v больше 67 всегда оцениваются как 300.


Как я уже сказал в вопросе, мне было интересно, есть ли другой путь
Питер Гибсон

3

Есть несколько очень хороших методов, упомянутых в книге Генри Уоррена «Восторг хакеров » и на его сайте hackersdelight.org . Для метода, который хорошо работает с микроконтроллерами меньшего размера при делении на константы, посмотрите этот файл .


Они выглядят хорошими для деления на константы, как вы говорите, но на самом деле не относятся к моей проблеме. Он использует такие методы, как предварительный расчет обратного - умножить на него, а затем сдвинуть.
Питер Гибсон

Это отличная книга!
Уинделл Оскай

3

Кажется, ваша функция не дает желаемого результата. Например, значение 50 возвращает примерно 302, а 100 возвращает примерно 300. Эти два результата почти не приведут к изменению скорости двигателя.

Если я вас правильно понимаю, вы действительно ищете быстрый способ сопоставить числа 1-100 с диапазоном 300-500 (приблизительно), чтобы 1 соответствовал 500, а 100 - 300.

Возможно, попробуйте: р = 500 - (2 * V)

Но я могу ошибаться - вы пытаетесь рассчитать время прямоугольной волны с постоянной частотой? Что за 298?


Да, спасибо, формула неверна. Суть в том, чтобы получить линейное ускорение от выхода шагового двигателя, изменяя целевую скорость на постоянную каждый временной интервал (скажем, скорость ++). Это должно быть сопоставлено с периодом (частотой), на который ребро + ve отправляется в контроллер шагового двигателя - отсюда и обратная зависимость (p = 1 / v).
Питер Гибсон

Вы имеете в виду постоянное ускорение, то есть линейно возрастающую скорость?
pingswept

Ах да, постоянное ускорение, я испортил это, когда первоначально писал вопрос, и помню, что тоже исправлял его
Питер Гибсон

3

Эффективный способ приблизить деления - сдвиги. например, если x = y / 103; деление на 103 - это то же самое, что и умножение на 0,0097087, поэтому для аппроксимации сначала выберите «хорошее» число смещения (т. е. число с основанием-2, 2,4,8,16,32 и т. д.)

Для этого примера 1024 хорошо подходит, так как мы можем сказать, что 10/1024 = 0,009765. Тогда можно кодировать:

х = (у * 10) >> 10;

Конечно, помня, что переменная y не переопределяет свой тип при умножении. Это не точно, но это быстро.


Это похоже на методы в ссылках, которые предоставил timrorr, и хорошо работает для деления на константы, но не для деления на значение, которое неизвестно во время компиляции.
Питер Гибсон

3

С другой стороны, если вы пытаетесь разделить процессор, который не поддерживает разделение, в этой статье Wiki есть действительно классный способ сделать это.

http://en.wikipedia.org/wiki/Multiplicative_inverse

Для аппроксимации обратной величины x, используя только умножение и вычитание, можно угадать число y, а затем многократно заменить y на 2y - xy2. Как только изменение y становится (и остается) достаточно малым, y является приближением обратной величины x.


Интересно, мне интересно, как это сравнивается с другими упомянутыми методами
Питер Гибсон

1

Этот процесс здесь выглядит тси удобно, хотя , возможно , потребуется немного портирования.

Хотя кажется, что LUT будет проще. Вам понадобится всего 100 байт, меньше, если вы используете некоторую интерполяцию, и, поскольку LUT заполнен константами, компилятор может даже найти его в области кода, а не в области данных.


Я пробовал что-то похожее в суммировании делителя до тех пор, пока он не станет равным или не превысит дивиденд, но оказалось, что он довольно медленный. Похоже, что LUT будет правильным способом - для использования avr-gcc вам понадобятся специальные макросы в <avr / progmem.h>, чтобы сохранить их во флэш-памяти.
Питер Гибсон

1

Убедитесь, что деление выполняется с плавающей запятой. Я использую Microchip, а не AVR, но при использовании C18 вам нужно заставить ваши литералы обрабатываться как числа с плавающей запятой. Например. Попробуйте изменить формулу на:

p = 202.0/v + 298.0;


1

Вы хотите быстро, так что все идет ..... Так как AVR не может эффективно выполнять нормализацию (смещение влево до тех пор, пока вы не можете больше смещаться), игнорируйте любые псевдо-алгоритмы с плавающей запятой. Самый простой способ очень точного и быстрого целочисленного деления в AVR - это обратная таблица поиска. В таблице будут храниться обратные величины, масштабированные большим числом (скажем, 2 ^ 32). Затем вы реализуете умножение unsigned32 x unsigned32 = unsigned 64 в ассемблере, поэтому answer = (Numberrator * inverseQ32 [denominator]) >> 32.
Я реализовал функцию умножения с использованием встроенного ассемблера (в функции ac). GCC поддерживает 64-битные "long long", однако, чтобы получить результат, вы должны умножить 64-битные на 64-битные, а не 32x32 = 64 из-за ограничений языка C на 8-битной архитектуре ......

Недостатком этого метода является то, что вы будете использовать 4K x 4 = 16K флэш-памяти, если хотите разделить на целые числа от 1 до 4096 ......

Очень точное беззнаковое деление теперь достигается примерно за 300 циклов в C.

Вы можете рассмотреть возможность использования 24-битных или 16-битных целых чисел для большей скорости, меньшей точности.


1
p = 202/v + 298; // p in us; v varies from 1->100

Возвращаемое значение вашего уравнения уже p=298таково, так как компилятор сначала делит, а затем добавляет, используйте целочисленное разрешение muldiv, которое:

p = ((202*100)/v + (298*100))/100 

Используя это то же умножение a*f, с = целое число f = дробь.

Это даёт, r=a*fно f=b/cпотом, r=a*b/cно пока не работает, потому что положение операторов, r=(a*b)/cфункция final или muldiv, способ вычисления дробных чисел с использованием только целых чисел.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.