Ефективний зворотний (1 / x) для 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 : При подальшому дослідженні розбіг працює над ардуїно, проблема була пов'язана як з неправильною формулою вище, так і з переливом int в іншому місці.


2
V ціле чи плаваюча точка?
mjh2007

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

Ви можете попередньо обчислити значення 100 цілих чисел і скласти таблицю пошуку попередніх шкал для множення, якщо вас дійсно хвилює швидкість. Звісно, ​​є компроміс пам'яті.
RYS

Відповіді:


7

Одна приємна річ, що стосується поділу - це те, що більш-менш всі це роблять. Це досить основна особливість мови C, і такі компілятори, як AVR-GCC (викликається ID Arduino IDE), оберуть найкращий доступний алгоритм поділу, навіть коли мікроконтролер не має інструкцій щодо апаратного поділу.

Іншими словами, вам не потрібно турбуватися про те, як здійснюється розподіл, якщо у вас не дуже дивний особливий випадок.


Якщо ви хвилюєтесь, вам може сподобатися читати офіційні запропоновані алгоритми поділу 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];

EDIT ви фактично лише таблицю пошуку 68 значень, оскільки значення v більше 67 завжди оцінюють до 300.


Як я вже говорив у запитанні, мені було цікаво, чи є інший шлях
Пітер Гібсон

3

У книзі "Хакерське захоплення Генрі Уоррена та на його веб-сайті hackersdelight.org" є кілька дуже хороших методик . Про техніку, яка добре працює з меншими мікроконтролерами при поділі на константи, ознайомтеся з цим файлом .


Вони виглядають добре для поділу на постійні, як ви кажете, але насправді не стосуються моєї проблеми. Він використовує прийоми, такі як попередній розрахунок оберненого - помножити на нього, а потім зрушити.
Пітер Гібсон

Це відмінна книга!
Вінделл Оскай

3

Ваша функція не здається, що вона дасть бажаний результат. Наприклад, значення 50 повертає приблизно 302, тоді як 100 повертає приблизно 300. Ці два результати не спричинить майже ніякої зміни швидкості двигуна.

Якщо я вас правильно зрозумів, ви дійсно шукаєте швидкий спосіб зіставити числа 1-100 до діапазону 300-500 (приблизно), такий, що 1 карта на 500 і 100 карт на 300.

Можливо, спробуйте: p = 500 - (2 * v)

Але я можу бути непорозумінням - ти намагаєшся обчислити час квадратної хвилі постійної частоти? Що 298?


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

Ви маєте на увазі постійне прискорення, тобто лінійно зростаючу швидкість?
pingswept

Ага так, постійне прискорення, я це приглушив, коли спочатку писав питання і пам’ятаю, що його там теж виправили
Пітер Гібсон

3

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

Для цього прикладу 1024 добре підходить, оскільки можна сказати, що 10/1024 = 0,009765 Його тоді можливо кодувати:

x = (y * 10) >> 10;

Згадуючи звичайно, щоб переконатися, що змінна y не переповнює її тип при множенні. Це не точно, але швидко.


Це схоже на методи в посиланнях, які постачав timrorr і добре працює для поділу на константи, але не при діленні на значення, невідоме під час компіляції.
Пітер Гібсон

3

Ще одна примітка, якщо ви намагаєтеся зробити поділ на процесорі, який не підтримує поділ, у цій статті Wiki це дійсно крутий спосіб.

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

Для наближення зворотного рівня x, використовуючи лише множення і віднімання, можна вгадати число y, а потім багаторазово замінити y на 2y - xy2. Як тільки зміна y стає (і залишається) достатньо малою, y є наближенням зворотної точки x.


Цікаво, мені цікаво, як це порівнюється з іншими згаданими методами
Пітером Гібсоном

1

Цей процес тут виглядає дружньо mcu, хоча він може потребувати трохи перенесення.

Хоча здається, що 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 = (чисельник * inverseQ32 [знаменник]) >> 32.
Я реалізував функцію множення за допомогою вбудованого асемблера, (загорнутого у функцію змінного струму). GCC підтримує 64-бітні "довгі довгі", однак, щоб отримати результат, вам потрібно помножити 64 біти на 64 біти, а не 32x32 = 64 через обмеження мови C у 8-бітній архітектурі ......

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

Дуже точний неподписаний поділ зараз досягнуто приблизно за 300 циклів у С.

Ви можете розглянути можливість використання 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або мульдів функцію, спосіб обчислити числа дріб, використовуючи лише цілі числа.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.