Ардуинодағы синезды (және косиналарды) есептеудің әртүрлі жолдары (және ең жылдам)

Мен жүйенің бұрыштарын есептеу үшін Arduino Uno тақтасын қолданамын (роботты қол). Бұрыштар ADC толық ауқымын қолдана отырып, шын мәнінде 10 биттік мәндер (0-ден 1023-ге дейін). Мен тек 1-ші квадрантта жұмыс істеймін (0-ден 90 градусқа дейін), онда синонимдер мен косиналардың екеуі де жағымды, сондықтан теріс сандармен проблема жоқ. Менің күмәнім 3 сұрақта:

  1. Arduino-да осы тригонометриялық функцияларды есептеудің әртүрлі әдісі қандай?

  2. Сол сияқты ең жылдам тәсілі?

  3. Arduino IDE-інде sin() және cos() функциялары бар, бірақ Arduino шын мәнінде оларды қалай есептейді (көрінетін кестелерді, жақындауларды және т.б. пайдаланады)? Олар айқын шешім ретінде көрінеді, бірақ мен оларды сынап көрмес бұрын олардың нақты іске асырылуын білгім келеді.

PS: Мен Arduino IDE және құрастыру кодының стандартты кодтауына, сондай-ақ айтылмаған басқа опцияларға ашықпын. Сондай-ақ, мен цифрлық жүйе үшін міндетті емес қателер мен жақындаулармен ешқандай проблемаларым жоқ; алайда мүмкіндігінше ықтимал қателердің дәрежесін атап өткен жөн

8
Мен градусқа жетуді қалаймын деп ойлаймын. Бұрыш үшін бүтін сандарды немесе ондық сандарды енгізу керек пе?
қосылды автор Yoni Baciu, көзі
Болжалды құндылықтармен жақсы боласыз ба?
қосылды автор Yoni Baciu, көзі
Сіздің дәлдігіңіздің қажеттілігін сандық бағалауға болады ма? Cos (π/2x) ≈ 1-x² жуықтауы 5.6e-2-те ең көп қателігі бар. Және (1-х²) (1-0,224х²), 3 көбейтуге арналған, 9.20e-4 аралығында жақсы.
қосылды автор Sprogz, көзі
Тек 90 (толық) градусқа 90 кіретін іздеу кестесі ең жылдам және тиімдірек болады. Шын мәнінде толық 360 градусқа 90 кірісті іздеу кестесін пайдалануға болады. Оны 90-179 дейін артқа қарай оқып шығыңыз және оны 180-269-ға айналдырыңыз. 270-359-ға тең.
қосылды автор Majenko, көзі
@EdgarBonet Кешіктірілген жауап үшін кешірім сұраймын. Мен кез-келген сандық белгіленген дәлдікте жоқ. Мен қазір барлық ықтимал нұсқаларын білгім келеді
қосылды автор Ken Arnold, көзі
Дәреже иә. Кодты жазуды және тестілеуді бүтін сандарды пайдаланатын болсақ, онда мен онымен айналысатын болар еді деп ойлаймын. Редакция туралы нақты ақпарат беремін
қосылды автор Ken Arnold, көзі
Иә, іс жүзінде, бірақ әртүрлі әдістердің қателігінің дәрежесін білгім келеді. Бұл дәлме-дәл өнім емес, сонымен қатар шахтадағы бүйірлік жоба. Математикалық функцияны жүзеге асыратын кез-келген цифрлық жүйе (кез келген жағдайда) үшін іс жүзінде жуықтау сөзсіз
қосылды автор Ken Arnold, көзі

8 жауаптар

Екі негізгі әдіс - бұл математикалық есептеу (полиномы бар) және іздеу кестелері.

Arduino-ның математикалық кітапханасы (libm, avr-libc бөлігі) бірінші пайдаланады. Ол AVR үшін оңтайландырылған, ол 100% жинау тілімен жазылған, сондықтан бұл іс-әрекетті орындау мүмкін емес (нөлдік пікірлер бар). Дегенмен, біздің оңтайландырылған таза флотты іске қосу миы бізден әлдеқайда жоғары болуы мүмкін екеніне сенімді болыңыз.

Алайда, float кілті бар. Ардуинодағы өзгермелі нүктеге қатысты кез келген нәрсе таза бүтін санмен салыстырғанда ауыр салмақты болады және сіз тек 0-ден 90 градусқа дейінгі бүтін сандарды сұрайтындықтан, қарапайым іздеу кестесі ең қарапайым және тиімді әдіс болып табылады.

91 мәндердің кестесі сізге 0-ден 90-ға дейін қоса береді. Дегенмен, сіз өзгермелі нүкте мәндерінің 0,0 және 1,0 арасындағы мәндер кестесін жасасаңыз, өзгермелі жұмыс істеудің тиімсіздігіне ие болуыңыз мүмкін (бұл жағдайда sin флотпен есептелінеді) орнына әлдеқайда тиімді болар еді.

1000-ға көбейтілген мәнді сақтау сияқты қарапайым болуы мүмкін, сондықтан сізде 0.0 және 1.0 арасында орнына 0-ден 1000-ға дейін бар (мысалы, күн (30) 500-ден 0,5-ке дейін сақталады). Мәндерді, мысалы, Q16 мәніне сақтау үшін неғұрлым тиімді болар еді, мұнда әр мән (бит) 1.0/65536/1-ді құрайды. Бұл Q16 мәндері (және онымен байланысты Q15, Q1.15, және т.б.) жұмыс істеу тиімдірек, себебі компьютерлер жұмыс істемейтін он-лайн күштерінің орнына жұмыс істеуге деген сүйіспеншілігі бар.

sin() функциясы радиандарды күтетінін ұмытпаңыз, сондықтан сіз алдымен бүтін градустарды жүзбелі нүктелі радиандар мәніне түрлендіріп, sin() бүтін дәрежелі мәнмен тікелей жұмыс істейтін іздеу кестесіне қарағанда әлдеқайда тиімсіз.

Алайда, екі сценарийдің комбинациясы мүмкін. Сызықтық интерполяция екі бүтін сандардың арасында өзгермелі нүктенің бұрышын жақындауға мүмкіндік береді. Қарап шығу кестесіндегі екі нүктенің арасындағы қаншалықты алыс екенін және екі мәннің осы қашықтыққа негізделген орташа мәнін жасау сияқты қарапайым. Мысалы, егер сіз 23,6 градуста болсаңыз, (sintable [23] * (1-0.6)) + (sintable [24] * 0.6) Негізінде сіздің синусоиды толқыны тікелей сызықтармен біріктірілген дискретті нүктелер қатарына айналады. Сіз жылдамдыққа қатысты дәлдікпен сауда жасайсыз.

8
қосылды
Мен бұрын кітапхананы жаздым, ол Тейлордың полиномиясын қолданды, ол кітапханаға қарағанда тезірек болды. Берілгендіктен, мен екі жағына да қосылыс нүктелік радиандарды қолдандым.
қосылды автор tuskiomi, көзі

Мұнда кейбір жақсы жауаптар бар, бірақ мен әлі айтылмаған әдісті қосқым келді, ол ендірілген жүйелерде есептеу тригонометриялық функцияларына өте ыңғайлы, бұл CORDIC әдісі Wiki кіру мұнда

5
қосылды
Мен бұрынғы тәсілдеме жасадым, stm8. sin (x), sin (x/2) мәнінен sin (x +/- x/2) мәнін есептеп, екі (2) қадамнан кейін sin (x) және cos (x) , cos (x) және cos (x/2) -> итерация арқылы мақсатыңызға жақындауға болады. менің жағдайымда мен 45 градустан (0,707) бастадым және мақсатыма шығу жолында жұмыс істедім. ол стандартты IAR sin() функциясынан айтарлықтай баяуырақ.
қосылды автор dannyf, көзі

Мен компьютерде синусын және косиналарын есептеуді үйрендім Ардуино тіркелген нүктелі полиномдық жуықтау әдістерімен. Міне менің Орта орындау уақытын өлшеу және ең қатерсіз қателік avr-libc стандартты cos() және sin()

function    max error   cycles   time
-----------------------------------------
cos_fix()   9.53e-5     108.25    6.77 µs
sin_fix()   9.53e-5     110.25    6.89 µs
cos()       2.98e-8     1720.8   107.5 µs
sin()       2.98e-8     1725.1   107.8 µs

It's based on a 6th degree polynomial computed with only 4 multiplications. The multiplications themselves are done in assembly, as I found that gcc implemented them inefficiently. The angles are expressed as uint16_t in units of 1/65536 of a revolution, which makes the arithmetic of angles naturally work modulo one revolution.

Егер сіз бұл заңға сәйкес келуі мүмкін деп ойласаңыз, мына код: Тұрақты нүкте тригонометрия . Кешіріңіз, мен француз тіліндегі бұл бетті әлі де аудармадым, бірақ сіз теңдеулерді және коды түсінуге болады (айнымалы атаулар, түсініктемелер ...) ағылшын тілінде.


Edit: Since the server seems to have vanished, here is some info on the approximations I found.

Мен екі бұрышты нүктелерді квадранттар бірліктерінде жазғым келді (Немесе, баламада, өз кезегінде). Сондай-ақ, мен біреуді қолданғым келді полиномиальды, өйткені олар ерікті қарағанда есептеу үшін неғұрлым тиімді полиномы. Басқаша айтқанда, мен осындай многочлена P() келеді

x ∈ [0, 1] үшін cos (π/2x) ≈ P (x 2

I also required the approximation to be exact at both ends of the interval, to ensure that cos(0) = 1 and cos(π/2) = 0. These constraints led to the form

P (u) = (1 - u) (1 + uQ (u))

онда Q() - еркін многочлена.

Содан кейін ең жақсы шешімімді дәреженің функциясы ретінде іздестірдім Q() және тапты:

        Q(u)             │ degree of P(x²) │ max error
─────────────────────────┼─────────────────┼──────────
          0              │         2       │  5.60e-2
       −0.224            │         4       │  9.20e-4
−0.2335216 + 0.0190963 u │         6       │  9.20e-6

Жоғарыдағы шешімдер арасында таңдау жылдамдық/дәлдікпен сауда-саттық болып табылады. The үшінші шешім 16 битпен қол жеткізуге қарағанда, дәлірек болады 16-битті іске асыру үшін таңдағаным.

5
қосылды
@TLW: жауапқа қосылды.
қосылды автор Sprogz, көзі
@LTL: Мен (1-x²) (1 + x²Q (x²)) түрінде шектелген кейбір «жақсы» қасиеттерді (мысалы, cos (0) = 1), мұнда Q (u) ерікті многодинамикалық (ол парақта түсіндіріледі). Мен бірінші дәрежелі Q (тек 2 коэффициент) алдым, шамамен сәйкес коэффициенттерді таптым, содан кейін сынақ және қате арқылы оңтайландыруды қолмен реттеді.
қосылды автор Sprogz, көзі
Бұл керемет, @Edgar.
қосылды автор SDsolar, көзі
Многодинаманы табу үшін не істедіңіз?
қосылды автор ThomasX, көзі
@EdgarBonet - қызықты. Назар аударыңыз, бұл бет маған жүктелмейді, бірақ кэштелген жұмыс істейді. Осы жауап үшін пайдаланылған мультииндікті қосыңыз ба?
қосылды автор ThomasX, көзі

Синхронды табудың ең жылдам жолы - іздеу кестесі. Егер сіз тіркелген нүктелі сандармен (екілік нүкте бит-0-ден оңнан басқа жерде болған бүтін сандар) ыңғайлы есептеулер жасасаңыз, синусымен бірге есептеулеріңіз әлдеқайда жылдам болады. Сол кесте RAM кеңістігін сақтау үшін Flash сөздері болуы мүмкін. Есіңізде болсын, математикада үлкен аралық нәтижелерге ұзақ уақыт қажет.

2
қосылды

Белгілі бір бұрыштың sin() және cos() анықтау үшін сызықтық жақындауды қолданатын бірнеше функцияларды жасай аласыз.

Мен осындай нәрсе ойлап жатырмын:
«сызықтық
Әрқайсысы үшін sin() және cos() сызбалық көріністерін 3 секцияға бөлдім және осы бөлімнің сызықты жуықтауын жасадым.

Сіздің функцияңыз алдымен періштенің диапазоны 0-ден 90-ға дейінгі аралығындағы екенін тексереді Сонда ол тиесілі 3 бөлімнің қайсысын анықтау үшін ifelse сөзін пайдаланады, содан кейін тиісті сызықтық есептеуді (яғни output = mX + c ) жасайды,

2
қосылды
Міндетті емес. Сізге 0-1-ден 0-ден 100-ке дейін масштабталуы мүмкін. Осылайша, өзгермелі нүкте емес, бүтін сандармен айналысады. Ескерту: 100 ерікті болды. Өнімді 0-128 немесе 0-512 немесе 0-1000 немесе 0-1024 аралығында масштабтау мүмкін болмады. 2-ден көп есе пайдаланып, нәтижені төменге түсіру үшін оңға жылжу керек.
қосылды автор Yoni Baciu, көзі
Өте ақылды, @sa_leinad. Upvote. Транзисторлармен жұмыс істеу кезінде бұл істі есіме түсіремін.
қосылды автор SDsolar, көзі
Бұл өзгермелі нүктені көбейтуді қамтуы мүмкін емес пе?
қосылды автор Ken Arnold, көзі

Мен cos() және sin() жақындаған басқа адамдарды іздестірдім және мына жауапты таптым:

dtb-тің жауабына «Fast Syn/Cos алдын ала есептелетін аударма жиынын пайдаланып»

Негізінен ол math.sin() функциясының математикалық кітапханасынан функционалды мәндер кестесін пайдаланудан жылдамырақ екенін есептеді. Бірақ айта аламын, бұл компьютерде есептеледі.

Arduino-ді sin() және cos() есептеуге болатын математикалық кітапхана бар.

2
қосылды
ДК-лерде оларды жылдам жасайтын FPU-лер бар. Ардуино емес, және бұл оны баяу етеді.
қосылды автор Majenko, көзі
Жауап сонымен қатар, жиым шекараларын тексеру сияқты нәрсені жасайтын C# үшін де қолданылады.
қосылды автор Longdaysjourneyintocode, көзі

generally, look-up table > approximation -> calculation. ram > flash. integer > fixed point > floating point. pre-calclation > real time calculation. mirroring (sine to cosine or cosine to sine) vs. actual calculation/look-up....

олардың әрқайсысы өзінің артықшылықтары мен кемшіліктеріне ие.

сіз қолдануға болатын ең жақсы жұмысын көру үшін әр түрлі комбинацияларды жасауға болады.

редакциялау: Мен жылдам тексеру жасадым. 8-биттік бүтін шығу арқылы 1024 күн мәнін есептеуге арналған кесте арқылы 0.6 мс, 133 мг сүзгілермен, немесе 200x баяу жүреді.

1
қосылды

Менің ОП-қа қатысты мәселе болды. Мен синхронды функцияның алғашқы квадрантын 0x8000-ден 0xffff-ға дейін белгісіз 16 бит бүтін сан ретінде есептеу үшін LUT кестесін жасауды қаладым. Мен бұл туралы көңілді және пайда табу үшін жаздым. Ескерту: Егер 'if' сөздері қолданылса, бұл тиімдірек жұмыс істейтін болады. Сондай-ақ, бұл өте дәл емес, бірақ дыбыстық синтезаторда синусоиды толқын үшін жеткілікті дәл болады

void sin_lut_ctor(){

//Make a Look Up Table for 511 terms of the sine function.
//Plugin in some polynomials to do some magic
//and you get an aproximation for sines up to π/2.
//

//All sines yonder π/2 can be derived with math

const uint16_t uLut_d = 0x0200; //maximum LUT depth for π/2 terms. 
uint16_t uLut_0[uLut_d];        //The LUT itself.
//Put the 2 above before your void setup() as global variables.
//This coefficients will only work for uLut_d = 511.

uint16_t arna_poly_0 = 0x000a;//11
uint16_t arna_poly_1 = 0x0001;//1
uint16_t arna_poly_2 = 0x0007;//7
uint16_t arna_poly_3 = 0x0001;//1   Precalculated Polynomials
uint16_t arna_poly_4 = 0x0001;//1   
uint16_t arna_poly_5 = 0x0007;//7
uint16_t arna_poly_6 = 0x0002;//2
uint16_t arna_poly_7 = 0x0001;//1

uint16_t Imm_UI_0 = 0x0001;             // Itterator
uint16_t Imm_UI_1 = 0x007c;             // An incrementor that decreases in time

uint16_t Imm_UI_2 = 0x0000;             // 
uint16_t Imm_UI_3 = 0x0000;             //             
uint16_t Imm_UI_4 = 0x0000;              //
uint16_t Imm_UI_5 = 0x0000;              //
uint16_t Imm_UI_6 = 0x0000;             // Temporary variables
uint16_t Imm_UI_7 = 0x0000;              //
uint16_t Imm_UI_8 = 0x0000;              //
uint16_t Imm_UI_9 = 0x0000;              //
uint16_t Imm_UI_A = 0x0000;
uint16_t Imm_UI_B = 0x0000;

uint16_t Imm_UI_A = uLut_d - 0x0001;    // 510

uLut_0[0x0000] = 0x8000;        //Assume that the middle point is 32768 (0x8000 hex)
while (Imm_UI_0 < Imm_UI_A) //Construct a quarter of the sine table
  {
Imm_UI_2++;                                   //Increase temporary variable by 1

Imm_UI_B = Imm_UI_2/arna_coeff_0;           //Divide it with the first coefficient (note: integer division)
Imm_UI_3 += Imm_UI_B;                         //Increase the next temporary value if the first one has increased up to the 1st coefficient
Imm_UI_1 -= Imm_UI_B;                         //Decrease the incrementor if this is the case
Imm_UI_2 *= 0x001 - Imm_UI_B;                 //Set the first temporary variable back to 0

Imm_UI_B = Imm_UI_3/arna_poly_1;           //Do the same thing as before with the next set of temporary variables
Imm_UI_4 += Imm_UI_B;
Imm_UI_1 -= Imm_UI_B;
Imm_UI_3 *= 0x0001 - Imm_UI_B;

Imm_UI_B = Imm_UI_4/arna_poly_2;           //And again... and again... you get the idea.
Imm_UI_5 += Imm_UI_B;
Imm_UI_1 -= Imm_UI_B;
Imm_UI_4 *= 0x0001 - Imm_UI_B;

Imm_UI_B = Imm_UI_5/arna_poly_3;
Imm_UI_6 += Imm_UI_B;
Imm_UI_1 -= Imm_UI_B;
Imm_UI_5 *= 0x0001 - Imm_UI_B;

Imm_UI_B = Imm_UI_6/arna_poly_4;
Imm_UI_7 += Imm_UI_B;
Imm_UI_1 -= Imm_UI_B;
Imm_UI_6 *= 0x0001 - Imm_UI_B;

Imm_UI_B = Imm_UI_7/arna_poly_5;
Imm_UI_8 += Imm_UI_B;
Imm_UI_1 -= Imm_UI_B;
Imm_UI_7 *= 0x0001 - Imm_UI_B;

Imm_UI_B = Imm_UI_8/arna_poly_6;
Imm_UI_9 += Imm_UI_B;
Imm_UI_1 -= Imm_UI_B;
Imm_UI_8 *= 0x0001 - Imm_UI_B;

Imm_UI_B = Imm_UI_9/arna_poly_7          //the last set won't need to increment a next variable so skip the step where you would increase it.
Imm_UI_1 -= Imm_UI_B;
Imm_UI_9 *= 1 - Imm_UI_B;

uLut_0[Imm_UI_0] = (uLut_0[Imm_UI_0 - 0x0001] + Imm_UI_1); //Set the current value as the previous one increased by our incrementor
Imm_UI_0++;              //Increase the itterator
  }   
  uLut_0[Imm_UI_A] = 0xffff; //Lastly, set the last value to 0xffff

  //And there you have it. A sine table with only one if statement (a while loop)
}

Енді мәндерді қайтару үшін осы функцияны пайдаланыңыз. Ол 0x0000-ден 0x0800-ға дейінгі мәнді қабылдайды және LUT

uint16_t lu_sin(uint16_t lu_val0)
{
  //Get a value from 0x0000 to 0x0800. Return an appropriate sin(value)
  Imm_UI_0 = lu_val0/0x0200; //determine quadrant
  Imm_UI_1 = lu_val0%0x0200; //Get which value
  if (Imm_UI_0 == 0x0000)
  {
    return uLut_0[Imm_UI_1];
  }
  if (Imm_UI_0 == 0x0001)
  {
    return uLut_0[0x01ff - Imm_UI_1];
  }
  if (Imm_UI_0 == 0x0002)
  {
    return 0xffff - uLut_0[Imm_UI_1];
  }
  if (Imm_UI_0 == 0x0003)
  {
    return 0xffff - uLut_0[0x01ff - Imm_UI_1];
  }
}// I'm using if statements here but similarly to the above code block, 
 //you can do without. just with integer divisions and modulos

Есіңізде болсын, бұл тапсырмадағы ең тиімді тәсіл емес, мен Тейлор сериалдарын тиісті ауқымда нәтиже беру жолын анықтау мүмкін емес.

1
қосылды
Сіздің кодыңыз құрастырылмайды: Imm_UI_A екі рет жарияланады, ; және кейбір айнымалы мәлімдемелер жоқ және uLut_0 жаһандық болуы керек. Қажетті түзетулермен lu_sin() жылдам (27 және 42 процессор циклдерінің арасында), бірақ өте дәл емес (максималды қате ≈ 5.04e-2). Мен осы «Arnadathian polynomials» тармағының мәнін ала алмаймын: өте ауыр есептеу болып көрінеді, бірақ нәтиже қарапайым квадрат жақындату сияқты нашар. Сонымен қатар, әдіс үлкен жадыға ие. Кестені ДК-ге есептеу және оны PROGMEM массиві ретінде бастапқы кодқа қою жақсы болар еді.
қосылды автор Sprogz, көзі