AVR için verimli ters (1 / x)


12

Bir AVR (veya yaklaşık) tersi hesaplamak için etkili bir yol bulmaya çalışıyorum.

Hızı doğrusal olarak değiştirebilmem için bir step motorun nabız süresini hesaplamaya çalışıyorum. Dönem, hızın tersi ile orantılıdır ( p = K/v), ancak bunu anında hesaplamanın iyi bir yolunu düşünemiyorum.

Benim formülüm

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

Arduino üzerinde yapılan testlerde, bölüm tamamen psabit bırakarak yok sayılıyor gibi görünüyor 298(belki de bu avr-gcc'de farklı olacaktır). Ben de vaşana kadar bir döngüde toplamaya çalıştım 202ve döngüler sayma, ama bu oldukça yavaş.

Bir arama tablosu oluşturabilir ve flash'ta depolayabilirim, ancak başka bir yol olup olmadığını merak ediyordum.

Edit : Belki başlık "verimli bölme" olmalıdır ...

Güncelleme : Pingwept'in işaret ettiği gibi, hızı dönemle eşleme formülü yanlış. Ancak asıl sorun bölme işlemidir.

Edit 2 : Daha fazla araştırmada, bölme arduino üzerinde çalışıyor, sorun hem yanlış formül hem de başka bir yerde bir int taşması nedeniyle oldu.


2
V bir tam sayı mı, kayar nokta mı?
mjh2007

Tamsayı, ama içimizde bir dönem verdiğinden, tamsayı bölünmesi burada yeterince doğrudur.
Peter Gibson

100 tamsayının değerlerini önceden hesaplayabilir ve gerçekten hız ile ilgileniyorsanız çarpma için ön ölçekleyicilerin bir arama tablosunu oluşturabilirsiniz. Tabii ki bir bellek değişimi var.
RYS

Yanıtlar:


7

Bölünme ile ilgili güzel bir şey, az çok herkesin bunu yapmasıdır. C dilinin oldukça temel bir özelliğidir ve AVR-GCC (Arduino IDE tarafından adlandırılır) gibi derleyiciler, mikrodenetleyici bir donanım bölme talimatına sahip olmasa bile mevcut en iyi bölme algoritmasını seçecektir.

Başka bir deyişle, çok garip bir özel durumunuz olmadıkça bölünmenin nasıl uygulandığı konusunda endişelenmenize gerek yoktur.


Endişelenirseniz, Atmel'in resmi önerilen bölüm algoritmalarını okumaktan hoşlanabilirsiniz (biri kod boyutu için optimize edilmiş ve biri yürütme hızı için optimize edilmiştir; ikisi de herhangi bir veri belleği almaz). İçerdeler:

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

Atmel sayfasında listelenen, "Arduino standartlarında kullanılan Atmega 168 ve Atmega 328 gibi (oldukça büyük) Atmega işlemcileri için" AVR200: Çarpma ve Bölme Rutinleri "Uygulama Notudur. Veri sayfaları ve uygulama notlarının listesi:

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


4

Bana tek ihtiyacınız olan 100 girişli bir arama tablosu gibi görünüyor. Bundan daha hızlı olamaz.

#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];

67 değerinden büyük olan v değerleri her zaman 300 olarak değerlendirildiğinden, aslında yalnızca 68 değer arama tablosunu DÜZENLEYİN .


Soruda söylediğim gibi, başka bir yol olup olmadığını merak ediyordum
Peter Gibson

3

" Henry Warren tarafından Hackers Delight ve kendi web sitesinde hackersdelight.org kitabında adı geçen bazı çok iyi teknikler var . Sabitler tarafından bölünürken daha küçük mikro denetleyiciler ile iyi çalışan bir teknik için bu dosyaya bir göz atın .


Bunlar dediğin gibi sabitler tarafından bölünmek için iyi görünüyor, ama sorunum için gerçekten geçerli değil. Tersini önceden hesaplayın - onunla çarpın, sonra kaydırın gibi teknikler kullanır.
Peter Gibson

Bu mükemmel bir kitap!
Windell Oskay

3

İşleviniz istediğiniz sonucu verecek gibi görünmüyor. Örneğin, 50 değeri yaklaşık 302, 100 değeri yaklaşık 300 olarak döner. Bu iki sonuç motorun hızında neredeyse hiçbir değişikliğe neden olmaz.

Sizi doğru anlarsam, 1-100 sayılarını 300-500 (yaklaşık) aralığına eşleştirmek için hızlı bir yol arıyorsunuz, böylece 1 500 ile 100 harita 300 ile eşleşir.

Belki de deneyin: p = 500 - (2 * v)

Ama yanlış anlayabilirim - sabit bir frekans kare dalgasının zamanında hesaplamaya mı çalışıyorsunuz? 298 nedir?


Evet teşekkürler, formül yanlış. Amaç, hedef hızı her zaman aralığında sabit bir hızda (hız ++ demek) değiştirerek stepin çıkışından doğrusal hızlanma elde etmektir. Bu, step motor kontrolörüne bir + ve kenarın gönderildiği döneme (frekans) eşlenmelidir - dolayısıyla ters ilişki (p = 1 / v).
Peter Gibson

Sabit ivmeyi mi yani doğrusal olarak artan hızı mı kastediyorsunuz?
pingswept

Ah evet, sürekli ivmelenme, ilk başta soruyu yazarken ve orada düzeltmeyi hatırladığımda bunu
Peter Gibson

3

Bölmeleri tahmin etmenin etkili bir yolu vardiyalardır. örneğin x = y / 103 ise; 103'e bölmek 0.0097087 ile çarpmakla aynıdır, bu yüzden bu rakamı ilk önce 'iyi' bir vites numarası seçin (yani bir taban-2 sayısı, 2,4,8,16,32 ve benzeri)

Bu örnek için 1024, 10/1024 = 0.009765 O zaman kodlamak mümkün olduğunu söyleyebiliriz:

x = (y * 10) >> 10;

Elbette, y değişkeninin çarpıldığında türünü taşmadığından emin olmak. Tam değil, ama hızlı.


Bu, timrorr'un sağladığı bağlantılardaki tekniklere benzer ve sabitler tarafından bölmek için iyi çalışır, ancak derleme zamanında bilinmeyen bir değere bölündüğünde değil.
Peter Gibson

3

Başka bir notta, bölmeyi desteklemeyen bir CPU'da bir bölme yapmaya çalışıyorsanız, bu Wiki makalesinde bunu yapmanın gerçekten harika bir yolu var.

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

Sadece çarpma ve çıkarma kullanarak x'in tersine yaklaşmak için, bir y sayısını tahmin edebilir ve sonra tekrar tekrar y'yi 2y - xy2 ile değiştirebilir. Y'deki değişim yeterince küçük hale geldiğinde (ve kaldığında), y, x'in tersine yaklaşımıdır.


İlginç, bunun bahsedilen diğer yöntemlerle nasıl karşılaştırıldığını merak ediyorum
Peter Gibson

1

Bu süreç burada da aktarımın bir ihtiyaçları olabilir gerçi, mcu dostu görünüyor.

Her ne kadar LUT daha kolay olurdu gibi görünüyor. Bazı enterpolasyon kullandıysanız daha az 100 bayta ihtiyacınız olacak ve LUT sabitlerle doldurulduğundan, derleyici veri alanı yerine kod alanında bile bulabilir.


Bölücüyü, kâr payına eşit veya aşana kadar toplamaya benzer bir şey denedim, ancak oldukça yavaş olduğunu gördüm. Görünüşe göre LUT gitmenin yolu olacak - avr-gcc'yi kullanarak <avr / progmem.h> dosyasında flash'ta saklamak için özel makrolara ihtiyacınız var.
Peter Gibson

1

Bölmenin kayan nokta olarak yapıldığından emin olun. AVR yerine Microchip kullanıyorum, ancak C18 kullanırken değişmez sayılarınızı kayan nokta olarak işlemeye zorlamanız gerekiyor. Örneğin. Formülünüzü şu şekilde değiştirmeyi deneyin:

p = 202.0/v + 298.0;


1

Bu yüzden hızlı gitmek istersiniz ..... AVR normalleştirmeyi verimli bir şekilde yapamayacağı için (artık değiştiremediğinize kadar sola kayıyor), yalancı kayan nokta algoritmalarını yok sayın. Bir AVR'de çok doğru ve en hızlı tamsayı bölmenin en basit yolu, karşılıklı bir arama tablosudur. Tablo, çok sayıda ölçeklenen karşılıkları depolayacaktır (2 ^ 32 diyelim). Daha sonra, bir unsigned32 x unsigned32 = unsigned 64 çarpımını birleştirici içinde uygularsınız, bu yüzden answer = (payet * inverseQ32 [payda]) >> 32.
Çarpma işlevini satır içi birleştirici (ac işlevine sarılmış) kullanarak uyguladım. GCC, 64 bitlik "uzun uzunlukları" destekliyor, ancak sonucu elde etmek için, 8 bit mimarideki C dili sınırlamaları nedeniyle 64 biti 64 bit ile çarpmanız gerekiyor, 32x32 = 64 değil ...

Bu yöntemin dezavantajı, 1'den 4096'ya kadar tamsayılara bölmek istiyorsanız 4K x 4 = 16K flaş kullanacaksınız ...

Şimdi C'de yaklaşık 300 döngüde çok hassas işaretsiz bölüm elde edilmektedir.

Daha fazla hız, daha az doğruluk için 24 bit veya 16 bit ölçekli tamsayıları kullanmayı düşünebilirsiniz.


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

Denkleminizin dönüş değeri zaten p=298derleyici önce bölündüğünden ve sonra ekleyin, tamsayı muldiv çözünürlüğünü kullanın:

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

Bunu kullanmak, çarpı a*f= a tamsayı f = kesirdir.

Bu verim, r=a*fancak f=b/cdaha sonra r=a*b/cişe yaramaz, çünkü işleçlerin konumu, r=(a*b)/cyalnızca tamsayı kullanarak kesir sayılarını hesaplayacak şekilde son veya muldiv işlevini verir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.