Hiệu quả nghịch đảo (1 / x) cho AVR


12

Tôi đang cố gắng tìm ra một cách hiệu quả để tính toán nghịch đảo trên một AVR (hoặc xấp xỉ nó).

Tôi đang cố gắng tính toán chu kỳ xung cho động cơ bước để tôi có thể thay đổi tốc độ tuyến tính. Khoảng thời gian tỷ lệ thuận với tốc độ nghịch đảo của tốc độ ( p = K/v), nhưng tôi không thể nghĩ ra một cách tốt để tính toán điều này một cách nhanh chóng.

Công thức của tôi là

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

Thử nghiệm trên Arduino, bộ phận dường như bị bỏ qua hoàn toàn pcố định tại 298(mặc dù có lẽ điều này sẽ khác trong avr-gcc). Tôi cũng đã thử tính tổng vtrong một vòng lặp cho đến khi vượt quá 202và đếm các vòng lặp, nhưng điều này khá chậm.

Tôi có thể tạo một bảng tra cứu và lưu trữ trong flash, nhưng tôi tự hỏi liệu có cách nào khác không.

Chỉnh sửa : Có thể tiêu đề phải là "phân chia hiệu quả" ...

Cập nhật : Khi pingswept chỉ ra, công thức của tôi để ánh xạ khoảng thời gian thành vận tốc là không chính xác. Nhưng vấn đề chính là hoạt động phân chia.

Chỉnh sửa 2 : Khi điều tra thêm, phân chia đang làm việc trên arduino, vấn đề là do cả công thức không chính xác ở trên và tràn int ở nơi khác.


2
Là một số nguyên hay dấu phẩy động?
mjh2007

Một số nguyên, nhưng vì nó cho một khoảng thời gian trong chúng ta, phép chia số nguyên là đủ chính xác ở đây.
Peter Gibson

Bạn có thể tính toán trước các giá trị của 100 số nguyên và lập bảng tra cứu các số nguyên trước để nhân nếu bạn thực sự quan tâm đến tốc độ. Tất nhiên là có một sự đánh đổi bộ nhớ.
RYS

Câu trả lời:


7

Một điều tốt đẹp về sự phân chia là ít nhiều ai cũng đang làm điều đó. Đây là một tính năng cốt lõi của ngôn ngữ C và các trình biên dịch như AVR-GCC (được gọi bởi Arduino IDE) sẽ chọn thuật toán phân chia tốt nhất hiện có, ngay cả khi vi điều khiển không có hướng dẫn phân chia phần cứng.

Nói cách khác, bạn không cần phải lo lắng về cách phân chia được thực hiện trừ khi bạn gặp trường hợp đặc biệt rất lạ.


Nếu bạn lo lắng, thì bạn có thể thích đọc các thuật toán phân chia được đề xuất chính thức của Atmel (một thuật toán được tối ưu hóa cho kích thước mã và một được tối ưu hóa cho tốc độ thực thi; không chiếm bất kỳ bộ nhớ dữ liệu nào). Họ đang ở trong:

http://www.atmel.com/dyn/resource/prod_document/doc0936.pdf

đó là Ứng dụng Lưu ý "AVR200: Các quy trình nhân và chia" được liệt kê trên trang Atmel cho các bộ xử lý Atmega (khá lớn) như Atmega 168 và Atmega 328 được sử dụng trong Arduinos tiêu chuẩn. Danh sách các bảng dữ liệu và ghi chú ứng dụng có tại:

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


4

Trông tôi giống như tất cả những gì bạn cần là một bảng tra cứu 100 mục. Không nhận được nhanh hơn nhiều.

#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 bạn thực sự chỉ có một bảng tra cứu giá trị 68 vì các giá trị của v lớn hơn 67 luôn ước tính là 300.


Như tôi đã nói trong câu hỏi, tôi đã tự hỏi liệu có cách nào khác không
Peter Gibson

3

Có một số kỹ thuật rất hay được đề cập trong cuốn sách "Hackers Delight của Henry Warren và trên trang web của anh ấy là hackersdelight.org . Đối với một kỹ thuật hoạt động tốt với các vi điều khiển nhỏ hơn khi chia cho các hằng số hãy xem tệp này .


Chúng có vẻ tốt cho việc chia theo các hằng số như bạn nói, nhưng không thực sự áp dụng cho vấn đề của tôi. Anh ta sử dụng các kỹ thuật như tính toán trước nghịch đảo - nhân với nó, sau đó thay đổi.
Peter Gibson

Đó là một cuốn sách tuyệt vời!
Windell Oskay

3

Chức năng của bạn không có vẻ như sẽ cho kết quả bạn muốn. Ví dụ: giá trị 50 trả về khoảng 302, trong khi 100 trả về khoảng 300. Hai kết quả đó sẽ gây ra hầu như không thay đổi tốc độ của động cơ.

Nếu tôi hiểu bạn một cách chính xác, bạn thực sự đang tìm kiếm một cách nhanh chóng để ánh xạ các số 1-100 đến phạm vi 300-500 (xấp xỉ), như 1 bản đồ thành 500 và 100 bản đồ thành 300.

Có lẽ thử: p = 500 - (2 * v)

Nhưng tôi có thể hiểu nhầm-- bạn đang cố tính thời gian của sóng vuông tần số không đổi? Cái gì vậy?


Vâng cảm ơn, công thức là sai. Vấn đề là để có được gia tốc tuyến tính từ đầu ra của bước, bằng cách thay đổi tốc độ mục tiêu theo hằng số mỗi khoảng thời gian (speed ++ say). Điều này phải được ánh xạ tới khoảng thời gian (tần số) mà cạnh + ve được gửi đến bộ điều khiển động cơ bước - do đó có mối quan hệ nghịch đảo (p = 1 / v).
Peter Gibson

Bạn có nghĩa là gia tốc không đổi, tức là vận tốc tăng tuyến tính?
pingswept

À đúng rồi, tăng tốc liên tục, tôi đã chế nhạo nó khi ban đầu viết câu hỏi và nhớ sửa nó ở đó nữa
Peter Gibson

3

Một cách hiệu quả để phân chia gần đúng là bằng ca. ví dụ: nếu x = y / 103; chia cho 103 cũng giống như nhân với 0,0097087, vì vậy để tính gần đúng số này trước tiên, hãy chọn số ca 'tốt' (tức là số cơ sở 2, 2,4,8,16,32, v.v.)

Trong ví dụ này, 1024 rất phù hợp vì chúng ta có thể nói rằng 10/1024 = 0,009765 Sau đó có thể viết mã:

x = (y * 10) >> 10;

Tất nhiên ghi nhớ để đảm bảo biến y không tràn kiểu của nó khi được nhân. Nó không chính xác, nhưng nó nhanh chóng.


Điều này tương tự với các kỹ thuật trong các liên kết mà timrorr cung cấp và hoạt động tốt để chia cho các hằng số, nhưng không phải khi chia cho một giá trị không xác định tại thời điểm biên dịch.
Peter Gibson

3

Một lưu ý khác nếu bạn đang cố gắng phân chia CPU mà không hỗ trợ phân chia thì có một cách thực sự hay để làm điều đó trong bài viết Wiki này.

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

Để tính gần đúng nghịch đảo của x, chỉ sử dụng phép nhân và phép trừ, người ta có thể đoán một số y và sau đó liên tục thay thế y bằng 2y - xy2. Khi thay đổi trong y trở thành (và duy trì) đủ nhỏ, y là một xấp xỉ của đối ứng của x.


Thật thú vị, tôi tự hỏi làm thế nào điều này so sánh với các phương pháp khác được đề cập
Peter Gibson

1

Quá trình này ở đây có vẻ thân thiện với mcu, mặc dù nó có thể cần một chút chuyển.

Mặc dù có vẻ như LUT sẽ dễ dàng hơn. Bạn sẽ chỉ cần 100 byte, ít hơn nếu bạn sử dụng một số phép nội suy và vì LUT chứa đầy các hằng số, trình biên dịch thậm chí có thể định vị nó trong vùng mã thay vì vùng dữ liệu.


Tôi đã thử một cái gì đó tương tự trong việc tổng hợp số chia cho đến khi nó bằng hoặc vượt quá mức cổ tức, nhưng thấy nó khá chậm. Có vẻ như LUT sẽ là con đường để đi - sử dụng avr-gcc, bạn cần các macro đặc biệt trong <avr / progmem.h> để lưu trữ trong flash.
Peter Gibson

1

Kiểm tra để đảm bảo rằng phép chia đang được thực hiện dưới dạng dấu phẩy động. Tôi sử dụng Microchip chứ không phải AVR, nhưng khi sử dụng C18, bạn cần buộc chữ của bạn được coi là điểm nổi. Ví dụ. Hãy thử thay đổi công thức của bạn thành:

p = 202.0/v + 298.0;


1

Bạn muốn nhanh chóng, vì vậy hãy đi ..... Vì không thể thực hiện chuẩn hóa một cách hiệu quả (dịch chuyển sang trái cho đến khi bạn không thể dịch chuyển nữa), bỏ qua mọi thuật toán dấu phẩy động giả. Cách đơn giản nhất để phân chia số nguyên rất chính xác và nhanh nhất trong một AVR là thông qua bảng tra cứu đối ứng. Bảng sẽ lưu trữ các đối ứng được chia tỷ lệ theo số lượng lớn (giả sử 2 ^ 32). Sau đó, bạn triển khai phép nhân unsign32 x unsign32 = phép nhân 64 không dấu trong trình biên dịch chương trình, vì vậy hãy trả lời = (tử số * inverseQ32 [mẫu số]) >> 32.
Tôi đã triển khai hàm nhân bằng cách sử dụng trình biên dịch nội tuyến, (được gói trong hàm ac). GCC không hỗ trợ "dài hạn" 64 bit, tuy nhiên, để có kết quả, bạn phải nhân 64 bit với 64 bit chứ không phải 32x32 = 64 do giới hạn ngôn ngữ C trên kiến ​​trúc 8 bit ......

Nhược điểm của phương pháp này là bạn sẽ sử dụng đèn flash 4K x 4 = 16K nếu bạn muốn chia cho các số nguyên từ 1 đến 4096 ......

Phân chia không dấu rất chính xác hiện đạt được trong khoảng 300 chu kỳ trong C.

Bạn có thể cân nhắc sử dụng các số nguyên có tỷ lệ 24 bit hoặc 16 bit để có tốc độ nhanh hơn, độ chính xác thấp hơn.


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

Giá trị trả về của phương trình của bạn là p=298do trình biên dịch chia trước rồi thêm, sử dụng độ phân giải muldiv nguyên:

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

Sử dụng điều này là nhân giống nhau a*f, với a = số nguyên f = phân số.

Điều đó mang lại r=a*fnhưng f=b/csau đó r=a*b/cnhưng nó không hoạt động vì vị trí của các toán tử, mang lại hàm cuối cùng r=(a*b)/choặc muldiv, một cách để tính các số phân số chỉ sử dụng số nguyên.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.