J'utilise une carte Arduino Uno pour calculer les angles de mon système (bras robotique). Les angles sont en fait des valeurs de 10 bits (0 à 1023) de l'ADC, en utilisant toute la gamme de l'ADC. Je ne vais opérer que dans le 1er quadrant (0 à 90 degrés), où les sinus et les cosinus sont positifs, donc il n'y a pas de problème avec les nombres négatifs. Mes doutes peuvent s'exprimer en 3 questions:
Quelles sont les différentes façons de calculer ces fonctions trigonométriques sur Arduino?
Quel est le moyen le plus rapide de faire de même?
Il y a les fonctions sin () et cos () dans l'IDE Arduino, mais comment l'Arduino les calcule-t-il réellement (car utilise-t-il des tables de recherche ou des approximations, etc.)? Ils semblent être une solution évidente, mais j'aimerais connaître leur mise en œuvre réelle avant de les essayer.
PS: Je suis ouvert au codage standard sur l'IDE Arduino et au codage d'assemblage, ainsi qu'à toute autre option non mentionnée. De plus, je n'ai aucun problème avec les erreurs et les approximations, qui sont inévitables pour un système numérique; cependant, si possible, il serait bon de mentionner l'étendue des erreurs possibles
la source
Réponses:
Les deux méthodes de base sont le calcul mathématique (avec polynômes) et les tables de recherche.
La bibliothèque mathématique d'Arduino (libm, partie de avr-libc) utilise la première. Il est optimisé pour l'AVR en ce qu'il est écrit avec un langage d'assemblage à 100%, et en tant que tel, il est presque impossible de suivre ce qu'il fait (il n'y a également aucun commentaire). Soyez assuré que ce sera le cerveau d'implémentation à flotteur pur le plus optimisé, bien supérieur au nôtre.
Cependant, la clé est flottante . Tout sur l'Arduino impliquant une virgule flottante va être lourd par rapport à un entier pur, et comme vous ne demandez que des entiers entre 0 et 90 degrés, une simple table de recherche est de loin la méthode la plus simple et la plus efficace.
Un tableau de 91 valeurs vous donnera tout de 0 à 90 inclus. Cependant, si vous créez une table de valeurs à virgule flottante entre 0,0 et 1,0, vous avez toujours l'inefficacité de travailler avec des flottants (accordé moins inefficace que le calcul
sin
avec des flottants), donc stocker une valeur à virgule fixe à la place serait beaucoup plus efficace.Cela peut être aussi simple que de stocker la valeur multipliée par 1000, vous avez donc entre 0 et 1000 au lieu entre 0,0 et 1,0 (par exemple, sin (30) serait stocké comme 500 au lieu de 0,5). Plus efficace serait de stocker les valeurs comme, par exemple, une valeur Q16 où chaque valeur (bit) représente 1 / 65536e de 1,0. Ces valeurs Q16 (et les Q15, Q1.15, etc.) associées sont plus efficaces avec lesquelles travailler, car vous avez des puissances de deux avec lesquelles les ordinateurs aiment travailler au lieu de puissances de dix avec lesquelles ils détestent travailler.
N'oubliez pas non plus que la
sin()
fonction attend des radians, vous devez donc d'abord convertir vos degrés entiers en une valeur en radians à virgule flottante, ce qui rend l'utilisationsin()
encore plus inefficace par rapport à une table de recherche qui peut fonctionner directement avec la valeur des degrés entiers.Une combinaison des deux scénarios est cependant possible. L'interpolation linéaire vous permettra d'obtenir une approximation d'un angle à virgule flottante entre deux entiers. C'est aussi simple que de déterminer la distance entre deux points dans la table de recherche et de créer une moyenne pondérée basée sur cette distance des deux valeurs. Par exemple, si vous êtes à 23,6 degrés, vous prenez
(sintable[23] * (1-0.6)) + (sintable[24] * 0.6)
. Fondamentalement, votre onde sinusoïdale devient une série de points discrets reliés entre eux par des lignes droites. Vous échangez la précision contre la vitesse.la source
Il y a de bonnes réponses ici, mais je voulais ajouter une méthode qui n'a pas encore été mentionnée, une très bien adaptée au calcul des fonctions trigonométriques sur les systèmes embarqués, et c'est la technique CORDIC Entrée Wiki ici Elle peut calculer des fonctions trigonométriques en utilisant uniquement des décalages et ajoute et une petite table de consultation.
Voici un exemple grossier en C. En effet, il implémente la fonction atan2 () des bibliothèques C en utilisant CORDIC (c'est-à-dire trouver un angle avec deux composantes orthogonales). Il utilise des virgules flottantes, mais peut être adapté pour une utilisation avec l'arithmétique à virgule fixe.
Mais essayez d'abord les fonctions natives d'Arduino trig - elles pourraient être assez rapides de toute façon.
la source
J'ai joué un peu avec le calcul des sinus et cosinus sur l'Arduino en utilisant des approximations polynomiales à virgule fixe. Voici mes mesures du temps d'exécution moyen et de l'erreur la plus défavorable, par rapport à la norme
cos()
etsin()
à avr-libc:Il est basé sur un polynôme du 6e degré calculé avec seulement 4 multiplications. Les multiplications elles-mêmes sont effectuées en assemblage, car j'ai trouvé que gcc les implémentait de manière inefficace. Les angles sont exprimés
uint16_t
en unités de 1/65536 de révolution, ce qui fait que l'arithmétique des angles fonctionne naturellement modulo d'une révolution.Si vous pensez que cela peut convenir à votre facture, voici le code: trigonométrie à virgule fixe . Désolé, je n'ai toujours pas traduit cette page, qui est en français, mais vous pouvez comprendre les équations, et le code (noms de variables, commentaires ...) est en anglais.
Edit : Puisque le serveur semble avoir disparu, voici quelques informations sur les approximations que j'ai trouvées.
Je voulais écrire des angles en virgule fixe binaire, en unités de quadrants (ou, de manière équivalente, en virages). Et je voulais également utiliser un polynôme pair, car ceux-ci sont plus efficaces à calculer que les polynômes arbitraires. En d'autres termes, je voulais un polynôme P () tel que
cos (π / 2 x) ≈ P (x 2 ) pour x ∈ [0,1]
J'ai également demandé que l'approximation soit exacte aux deux extrémités de l'intervalle, pour garantir que cos (0) = 1 et cos (π / 2) = 0. Ces contraintes ont conduit à la forme
P (u) = (1 - u) (1 + uQ (u))
où Q () est un polynôme arbitraire.
Ensuite, j'ai cherché la meilleure solution en fonction du degré de Q () et j'ai trouvé ceci:
Le choix parmi les solutions ci-dessus est un compromis vitesse / précision. La troisième solution donne plus de précision que ce qui est réalisable avec 16 bits, et c'est celle que j'ai choisie pour l'implémentation 16 bits.
la source
Vous pouvez créer quelques fonctions qui utilisent l'approximation linéaire pour déterminer le sin () et le cos () d'un angle particulier.
Je pense à quelque chose comme ceci: Pour chacun, j'ai divisé la représentation graphique de sin () et cos () en 3 sections et j'ai fait une approximation linéaire de cette section.
Idéalement, votre fonction vérifierait d'abord que la plage de l'ange est comprise entre 0 et 90.
Ensuite, elle utiliserait une
ifelse
déclaration pour déterminer à laquelle des 3 sections il appartient, puis effectuerait le calcul linéaire correspondant (c.-à-d.output = mX + c
)la source
J'ai cherché d'autres personnes qui avaient approché cos () et sin () et je suis tombé sur cette réponse:
Réponse de dtb à "Fast Sin / Cos utilisant un tableau de traduction pré-calculé"
Fondamentalement, il a calculé que la fonction math.sin () de la bibliothèque mathématique était plus rapide que l'utilisation d'une table de recherche de valeurs. Mais d'après ce que je peux dire, cela a été calculé sur un PC.
Arduino a une bibliothèque mathématique incluse qui peut calculer sin () et cos ().
la source
Une table de recherche sera le moyen le plus rapide de trouver des sinus. Et si vous êtes à l'aise avec les nombres à virgule fixe (entiers dont le point binaire est ailleurs qu'à droite du bit-0), vos calculs ultérieurs avec les sinus seront également beaucoup plus rapides. Ce tableau peut alors être un tableau de mots, éventuellement en Flash pour économiser de l'espace RAM. Notez que dans vos calculs, vous devrez peut-être utiliser des longs pour de grands résultats intermédiaires.
la source
généralement, table de correspondance> approximation -> calcul. ram> flash. entier> virgule fixe> virgule flottante. pré-calclation> calcul en temps réel. mise en miroir (sinus à cosinus ou cosinus à sinus) par rapport au calcul / recherche réel ...
chacun a ses avantages et ses inconvénients.
vous pouvez faire toutes sortes de combinaisons pour voir celle qui convient le mieux à votre application.
edit: j'ai fait une vérification rapide. en utilisant une sortie entière de 8 bits, le calcul de 1024 valeurs de sinistre avec la table de recherche prend 0,6 ms et 133 ms avec des flottants, ou 200 fois plus lentement.
la source
J'avais une question similaire à OP. Je voulais créer une table LUT pour calculer le premier quadrant de la fonction sinus en tant qu'entiers 16 bits non signés à partir de 0x8000 à 0xffff. Et j'ai fini par écrire ceci pour le plaisir et le profit. Remarque: Cela fonctionnerait plus efficacement si j'utilisais des instructions «if». De plus, ce n'est pas très précis, mais serait suffisamment précis pour une onde sinusoïdale dans un synthétiseur de son
Maintenant, pour récupérer les valeurs, utilisez cette fonction qui accepte une valeur de 0x0000 à 0x0800 et renvoie la valeur appropriée de la LUT
Rappelez-vous, ce n'est pas l'approche la plus efficace pour cette tâche, je n'arrivais pas à comprendre comment faire des séries Taylor pour donner des résultats dans la plage appropriée.
la source
Imm_UI_A
est déclaré deux fois, a;
et certaines déclarations de variables sont manquantes etuLut_0
doivent être globales. Avec les correctifs nécessaires,lu_sin()
est rapide (entre 27 et 42 cycles CPU) mais très imprécis (erreur maximale ≈ 5.04e-2). Je ne peux pas comprendre ce que sont ces «polynômes Arnadathiens»: cela semble un calcul assez lourd, mais le résultat est presque aussi mauvais qu'une simple approximation quadratique. La méthode a également un coût de mémoire énorme. Il serait préférable de calculer la table sur votre PC et de la placer dans le code source sous forme dePROGMEM
tableau.Juste pour le plaisir, et pour prouver que cela peut être fait, j'ai terminé une routine d'assemblage AVR pour calculer les résultats sin (x) en 24 bits (3 octets) avec un bit d'erreur. L'angle d'entrée est en degrés avec un chiffre décimal, de 000 à 900 (0 ~ 90,0) pour le premier quadrant uniquement. Il utilise moins de 210 instructions AVR et fonctionne en moyenne sur 212 microsecondes, variant de 211us (angle = 001) à 213us (angle = 899).
Il a fallu plusieurs jours pour tout faire, plus de 10 jours (heures gratuites) en pensant simplement au meilleur algorithme pour le calcul, compte tenu du microcontrôleur AVR, sans virgule flottante, éliminant toutes les divisions possibles. Ce qui a pris plus de temps, c'était de faire les bonnes valeurs de passage pour les entiers, pour avoir une bonne précision, il faut passer des valeurs de 1e-8 aux nombres entiers binaires 2 ^ 28 ou plus. Une fois que toutes les erreurs responsables de la précision et de l'arrondi ont été trouvées, ont augmenté leur résolution de calcul de 2 ^ 8 ou 2 ^ 16 supplémentaires, les meilleurs résultats ont été obtenus. J'ai d'abord simulé tous les calculs sur Excel en prenant soin d'avoir toutes les valeurs comme Int (x) ou Round (x, 0) pour représenter exactement le traitement de base AVR.
Par exemple, dans l'algorithme, l'angle doit être en radians, l'entrée est en degrés pour faciliter la tâche de l'utilisateur. Pour convertir des degrés en radians, la formule triviale est rad = degrés * PI / 180, cela semble agréable et facile, mais ce n'est pas le cas, PI est un nombre infini - si l'utilisation de quelques chiffres crée des erreurs en sortie, la division par 180 nécessite Manipulation de bits AVR car il n'a pas d'instruction de division, et plus que cela, le résultat nécessiterait une virgule flottante car implique des nombres bien inférieurs à l'entier 1. Par exemple, Radian de 1 ° (degré) est 0,017453293. Puisque PI et 180 sont des constantes, pourquoi ne pas inverser cette chose pour une simple multiplication? PI / 180 = 0,017453293, multipliez-le par 2 ^ 32 et il en résulte une constante 74961320 (0x0477D1A8), multipliez ce nombre par votre angle en degrés, disons 900 pour 90 ° et décalons-le de 4 bits vers la droite (÷ 16) pour obtenir 4216574250 (0xFB53D12A), c'est-à-dire les radians du 90 ° avec une expansion de 2 ^ 28, ajustés en 4 octets, sans une seule division (sauf le 4 décalage de bit à droite). D'une certaine manière, l'erreur incluse dans une telle astuce est inférieure à 2 ^ -27.
Donc, tous les autres calculs doivent se rappeler qu'il est 2 ^ 28 plus élevé et pris en charge. Vous devez diviser les résultats en déplacement par 16, 256 ou même 65536 juste pour éviter qu'il n'utilise des octets de faim croissants inutiles qui n'aideraient pas la résolution. Ce fut un travail minutieux, il suffit de trouver la quantité minimale de bits dans chaque résultat de calcul, en maintenant la précision des résultats autour de 24 bits. Chacun des plusieurs calculs a été effectué en essai / erreur avec un nombre de bits supérieur ou inférieur dans le flux Excel, en regardant la quantité globale de bits d'erreur au résultat dans un graphique montrant 0-90 ° avec une macro exécutant le code 900 fois, une fois par dixième de degré. Cette approche Excel "visuelle" était un outil que j'ai créé, qui m'a beaucoup aidé à trouver la meilleure solution pour chaque partie du code.
Par exemple, en arrondissant ce résultat de calcul particulier 13248737.51 à 13248738 ou tout simplement perdre les décimales "0,51", dans quelle mesure cela affectera-t-il la précision du résultat final pour tous les 900 angles d'entrée (00,1 ~ 90,0) tests?
J'ai pu garder l'animal contenu dans 32 bits (4 octets) à chaque calcul, et je me suis retrouvé avec la magie pour obtenir une précision dans les 23 bits du résultat. Lors de la vérification des 3 octets du résultat, l'erreur est de ± 1 LSB, en suspens.
L'utilisateur peut saisir un, deux ou trois octets du résultat pour ses propres exigences de précision. Bien sûr, si un seul octet est suffisant, je recommanderais d'utiliser une seule table sin de 256 octets et d'utiliser l'instruction AVR 'LPM' pour la saisir.
Une fois que la séquence Excel a été fluide et nette, la traduction finale d'Excel vers l'assemblage AVR a pris moins de 2 heures, comme d'habitude, vous devriez penser plus en premier, travailler moins plus tard.
À ce moment-là, j'ai pu en serrer encore plus et réduire l'utilisation des registres. Le code réel (non définitif) utilise environ 205 instructions (~ 410 octets), exécute un calcul sin (x) en moyenne de 212us, horloge à 16 MHz. À cette vitesse, il peut calculer 4700+ sin (x) par seconde. Ce n'est pas important, mais il peut exécuter une sinusoïde précise jusqu'à 4700 Hz avec 23 bits de précision et de résolution, sans aucune table de recherche.
L'algorithme de base est basé sur la série Taylor pour sin (x), mais a beaucoup changé pour s'adapter à mes intentions avec le microcontrôleur AVR et la précision à l'esprit.
Même que l'utilisation d'une table de 2700 octets (900 entrées * 3 octets) serait une vitesse intéressante, quelle est l'expérience amusante ou d'apprentissage à ce sujet? Bien sûr, l'approche CORDIC a également été envisagée, peut-être plus tard, le point ici est de presser Taylor dans le cœur de l'AVR et de prendre l'eau d'une roche sèche.
Je me demande si Arduino "sin (78.9 °)" peut exécuter Processing (C ++) avec 23 bits de précision en moins de 212us et le code nécessaire plus petit que 205 instructions. Peut-être si C ++ utilise CORDIC. Les croquis Arduino peuvent importer du code d'assemblage.
Cela n'a aucun sens de publier le code ici, plus tard, je modifierai ce message pour y inclure un lien Web, peut-être sur mon blog à cette URL . Le blog est principalement en portugais.
Cette entreprise sans passe-temps était intéressante, repoussant les limites du moteur AVR de près de 16MIPS à 16MHz, sans instruction de division, multiplication uniquement en 8x8 bits. Il permet de calculer sin (x), cos (x) [= sin (900-x)] et tan (x) [= sin (x) / sin (900-x)].
Surtout, cela a aidé à garder mon cerveau de 63 ans poli et huilé. Quand les adolescents disent que les «vieux» ne savent rien de la technologie, je réponds «détrompez-vous, qui pensez-vous a créé les bases de tout ce que vous aimez aujourd'hui?».
À votre santé
la source
sin()
fonction standard a à peu près la même précision que la vôtre et est deux fois plus rapide. Il est également basé sur un polynôme. 2. Si un angle arbitraire doit être arrondi au multiple de 0,1 ° le plus proche, cela peut entraîner une erreur d'arrondi aussi élevée que 8,7e-4, ce qui annule en quelque sorte le bénéfice de la précision de 23 bits. 3. Pourriez-vous partager votre polynôme?Comme d'autres l'ont mentionné, les tables de recherche sont la voie à suivre si vous voulez de la vitesse. J'ai récemment étudié le calcul des fonctions trigonométriques sur un ATtiny85 pour l'utilisation de moyennes vectorielles rapides (vent dans mon cas). Il y a toujours des compromis ... pour moi, je n'avais besoin que d'une résolution angulaire de 1 degré, donc une table de recherche de 360 int (mise à l'échelle de -32767 à 32767, ne fonctionnant qu'avec des int) était la meilleure solution. Récupérer le sinus est juste une question de fournir un index 0-359 ... donc très vite! Quelques chiffres de mes tests:
Temps de recherche FLASH (us): 0.99 (table stockée à l'aide de PROGMEM)
Temps de recherche RAM (us): 0,69 (table en RAM)
Lib time (us): 122.31 (en utilisant Arduino Lib)
Notez que ce sont des moyennes sur un échantillon de 360 points pour chacun. Les tests ont été effectués sur un nano.
la source