ผกผันมีประสิทธิภาพ (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 : ในการตรวจสอบเพิ่มเติมหารกำลังทำงานกับ arduino ปัญหาเกิดจากทั้งสูตรที่ไม่ถูกต้องด้านบนและ int ล้นที่อื่น


2
v เป็นจำนวนเต็มหรือทศนิยม
mjh2007

จำนวนเต็ม แต่เมื่อให้ระยะเวลากับเราการหารจำนวนเต็มจึงแม่นยำพอที่นี่
Peter Gibson

คุณสามารถคำนวณค่าของจำนวนเต็ม 100 ล่วงหน้าและสร้างตารางการค้นหาของผู้คัดสรรล่วงหน้าสำหรับการคูณหากคุณกังวลเรื่องความเร็ว แน่นอนว่ามีการแลกเปลี่ยนหน่วยความจำออก
RYS

คำตอบ:


7

สิ่งหนึ่งที่ดีเกี่ยวกับการแบ่งคือทุกคนทำมาก มันเป็นคุณสมบัติหลักที่สวยงามของภาษา C และคอมไพเลอร์เช่น AVR-GCC (เรียกว่าโดย Arduino IDE) จะเลือกอัลกอริทึมการหารที่ดีที่สุดที่มีให้แม้ว่าไมโครคอนโทรลเลอร์จะไม่มีคำสั่งการแบ่งฮาร์ดแวร์

กล่าวอีกนัยหนึ่งคุณไม่จำเป็นต้องกังวลเกี่ยวกับวิธีการใช้งานการแบ่งถ้าคุณมีกรณีพิเศษที่แปลกมาก


หากคุณกังวลคุณอาจสนุกกับการอ่านอัลกอริธึมการแบ่งอย่างเป็นทางการของ Atmel (อันที่เหมาะสำหรับขนาดโค้ดและอีกอันหนึ่งที่เหมาะสำหรับความเร็วในการประมวลผล; ไม่ต้องใช้หน่วยความจำข้อมูลใด ๆ ) พวกเขาอยู่ใน:

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

ซึ่งเป็นหมายเหตุของแอปพลิเคชัน "AVR200: Multiply และ Divide Routine" แสดงอยู่ในหน้า 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 เสมอ


ดังที่ฉันพูดในคำถามฉันสงสัยว่ามีวิธีอื่นหรือไม่
Peter Gibson

3

มีเทคนิคที่ดีมากที่กล่าวถึงในหนังสือ"แฮ็คเกอร์ดีไลท์โดยเฮนรีวอร์เรนและเว็บไซต์ของเขาแฮ็กเกอร์เดไลท์ . orgสำหรับเทคนิคที่ทำงานได้ดีกับไมโครคอนโทรลเลอร์ขนาดเล็กเมื่อแบ่งตามค่าคงที่ได้ดูไฟล์นี้


สิ่งเหล่านี้ดูดีสำหรับการหารด้วยค่าคงที่ตามที่คุณพูด แต่ไม่ได้ใช้กับปัญหาของฉันจริงๆ เขาใช้เทคนิคต่าง ๆ เช่นการคำนวณอินเวอร์ส - คูณมันแล้วเปลี่ยน
Peter Gibson

นั่นเป็นหนังสือที่ยอดเยี่ยม!
Windell Oskay

3

ฟังก์ชั่นของคุณดูเหมือนจะให้ผลลัพธ์ตามที่คุณต้องการ ตัวอย่างเช่นค่า 50 ส่งคืนประมาณ 302 ในขณะที่ 100 ส่งคืนค่าประมาณ 300 ผลลัพธ์ทั้งสองนั้นจะทำให้ความเร็วของมอเตอร์แทบจะไม่เปลี่ยนแปลงเลย

ถ้าฉันเข้าใจคุณอย่างถูกต้องคุณกำลังมองหาวิธีที่รวดเร็วในการจับคู่หมายเลข 1-100 กับช่วง 300-500 (โดยประมาณ) เช่นนั้นแผนที่ 1 ถึง 500 และ 100 ถึง 300

อาจลอง: p = 500 - (2 * v)

แต่ฉันอาจเข้าใจผิด - คุณกำลังพยายามคำนวณเวลาของคลื่นสี่เหลี่ยมที่มีความถี่คงที่หรือไม่? 298 คืออะไร


ใช่ขอบคุณสูตรไม่ถูกต้อง จุดคือการได้รับการเร่งความเร็วเชิงเส้นจากเอาท์พุทของ stepper โดยการเปลี่ยนแปลงความเร็วเป้าหมายโดยค่าคงที่ในแต่ละช่วงเวลา (speed ++ พูด) สิ่งนี้จะต้องถูกแมปกับช่วงเวลา (ความถี่) ที่ขอบ + ve ถูกส่งไปยังตัวควบคุมมอเตอร์สเต็ปดังนั้นความสัมพันธ์แบบผกผัน (p = 1 / v)
Peter Gibson

คุณหมายถึงการเร่งอย่างต่อเนื่องนั่นคือความเร็วที่เพิ่มขึ้นเป็นเส้นตรงหรือไม่?
peptwept

อ่าใช่เร่งคงที่ผม mucked ว่าขึ้นเมื่อ แต่เดิมเขียนคำถามและจำไว้ว่าการแก้ไขมันมีมากเกินไป
ปีเตอร์กิบสัน

3

วิธีที่มีประสิทธิภาพในการหารโดยประมาณคือการเลื่อน เช่นถ้า x = y / 103; หารด้วย 103 จะเท่ากับการคูณด้วย 0.0097087 ดังนั้นเมื่อต้องการประมาณนี้ก่อนให้เลือกหมายเลขกะ 'ดี' (เช่นหมายเลขฐาน -2, 2,4,8,16,32 เป็นต้น)

สำหรับตัวอย่างนี้ 1024 เป็นแบบที่ดีเพราะเราสามารถพูดได้ว่า 10/1024 = 0.009765 มันเป็นไปได้ที่จะโค้ด:

x = (y * 10) >> 10;

การจดจำแน่นอนเพื่อให้แน่ใจว่าตัวแปร y จะไม่ล้นประเภทเมื่อคูณ มันไม่ถูกต้อง แต่มันรวดเร็ว


ซึ่งคล้ายกับเทคนิคในลิงก์ที่ timrorr จัดให้และทำงานได้ดีสำหรับการหารด้วยค่าคงที่ แต่ไม่เมื่อทำการหารด้วยค่าที่ไม่ทราบ ณ เวลารวบรวม
Peter Gibson

3

ในบันทึกอื่นถ้าคุณพยายามแบ่ง CPU ที่ไม่รองรับการแบ่งมีวิธีที่ยอดเยี่ยมในบทความ Wiki นี้

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

เพื่อประมาณค่าส่วนกลับของ x โดยใช้การคูณและการลบเพียงอย่างเดียวเราสามารถเดาตัวเลข y แล้วจึงแทนที่ y ด้วย 2y - xy2 ซ้ำ ๆ เมื่อการเปลี่ยนแปลงของ y กลายเป็น (และยังคงอยู่) ที่มีขนาดเล็กเพียงพอ y จะเป็นการประมาณของส่วนกลับของ x


น่าสนใจฉันสงสัยว่าวิธีนี้เปรียบเทียบกับวิธีอื่น ๆ ที่กล่าวถึงอย่างไร
Peter Gibson

1

กระบวนการนี้ที่นี่ดูเป็นมิตรกับ mcu ถึงแม้ว่ามันอาจต้องใช้การพอร์ตสักหน่อย

แม้ว่าดูเหมือนว่า LUT จะง่ายขึ้น คุณต้องการเพียง 100 ไบต์เท่านั้นหากคุณใช้การประมาณค่าน้อยและเนื่องจาก LUT เต็มไปด้วยค่าคงที่ดังนั้นคอมไพเลอร์อาจค้นหาตำแหน่งในพื้นที่โค้ดแทนพื้นที่ข้อมูล


ฉันลองสิ่งที่คล้ายกันในการรวมตัวหารจนกว่ามันจะเท่ากับหรือเกินกว่าเงินปันผล แต่พบว่ามันค่อนข้างช้า ดูเหมือนว่า LUT จะเป็นหนทางไป - โดยใช้ avr-gcc คุณต้องมีมาโครพิเศษใน <avr / progmem.h> เพื่อเก็บไว้ในแฟลช
Peter Gibson

1

ตรวจสอบเพื่อให้แน่ใจว่าแผนกกำลังดำเนินการเป็นจุดลอย ฉันใช้ Microchip ไม่ใช่ AVR แต่เมื่อใช้ C18 คุณต้องบังคับให้ตัวอักษรของคุณได้รับการปฏิบัติเหมือนเป็นจุดลอยตัว เช่น. ลองเปลี่ยนสูตรเป็น:

p = 202.0/v + 298.0;


1

คุณต้องการได้อย่างรวดเร็วดังนั้นที่นี่ไป ..... ตั้งแต่ AVR ไม่สามารถทำให้ปกติได้อย่างมีประสิทธิภาพ (เลื่อนไปทางซ้ายจนกว่าคุณจะไม่สามารถขยับได้อีกต่อไป) ให้ละเว้นอัลกอริธึมหลอกจุดหลอก วิธีที่ง่ายที่สุดสำหรับการหารจำนวนเต็มที่แม่นยำและเร็วที่สุดใน AVR คือผ่านตารางการค้นหาแบบส่วนกลับ ตารางจะจัดเก็บส่วนกลับซึ่งปรับขนาดเป็นจำนวนมาก (พูด 2 ^ 32) จากนั้นคุณใช้ unsigned32 x unsigned32 = การคูณ 64 ที่ไม่ได้ลงชื่อในแอสเซมเบลอร์ดังนั้น answer = (ตัวเศษ * inverseQ32 [ตัวหาร]) >> 32.
ฉันใช้ฟังก์ชั่นการคูณโดยใช้แอสเซมเบลอร์อินไลน์ GCC สนับสนุน 64 บิต "long longs" เพื่อให้ได้ผลลัพธ์คุณต้องคูณ 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หรือ muldiv ฟังก์ชั่นอย่างกับตัวเลขส่วนการคำนวณโดยใช้เพียงจำนวนเต็ม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.