Question:
IIR filter on a 16bit MCU (PIC24F)
rasgo
2011-04-03 20:31:11 UTC
view on stackexchange narkive permalink

J'essaie d'implémenter un simple filtre IIR de premier ordre sur un MCU (PIC24FJ32GA002), sans succès jusqu'à présent. Le filtre est un filtre de suivi CC (filtre passe-bas) dont le but est de suivre la composante CC d'un signal de 1,5 Hz. L'équation de différence est tirée d'une note d'application TI:

y (n) = K x (n) + y (n-1) (1-K)
avec K = 1/2 ^ 8

J'ai fait un script MATLAB pour le tester et ça marche bien dans la simulation.Code utilisé:

  K = 1/2 ^ 8b = Ka = [1 - (1-K)] Fs = 200; // fréquence d'échantillonnage Ts = 1 / Fs; Nx = 5000; // nombre d'échantillons nT = Ts * (0: Nx-1); fin = 1,5; // fréquence du signalrandn ('état', somme (100 * horloge)); noise = randn (1, Nx); noise = noise-mean (noise); xin = 200 + 9 * (cos (2 * pi * fin * nT)); xin = xin + noise; out = filter (b, a, xin);  

Simulation Frequence response

Cependant, je ne peux pas implémentez-le sur un microcontrôleur PIC24F. Je représente les coefficients au format Q15 (1.15), en les stockant dans des variables courtes et en utilisant une longue pour les multiplications. Voici le code:

  short xn; short y; short b0 = 128, a1 = 32640; // Q15long aux1, aux2; // (...) while (1) {xn = readADC (adc_ch); aux1 = ((long) b0 * xn) << 1; aux2 = ((long) a1 * y) << 1; y = ((aux1 + aux2) >> 16); delay_ms (5);}  

Un cast long est utilisé pour étendre le signal afin que l'opération de multiplication soit effectuée correctement. Après chaque multiplication, je décale un bit vers la gauche pour supprimer le bit de signal étendu. Lors de la sommation, je décale 16 bits vers la droite pour obtenir y au format Q15.

Je débogue le MCU avec Pickit2 et la fenêtre «View-> Watch» (MPLAB IDE 8.53) et teste le filtre avec un signal CC (I changer le signal DC avec un potentiomètre pour tester différentes valeurs). L'ADC a une résolution de 10 bits et le MCU est fourni avec 3,3 V. Quelques résultats:

1V -> xn = 312 (correct), yn = 226 (incorrect)
1,5V -> xn = 470 (correct), yn = 228 (complètement faux)

Qu'est-ce que je fais de mal? Des suggestions sur la façon d'implémenter ce filtre IIR sur un MCU 16 bits?

Merci d'avance :)

Six réponses:
user3624
2011-04-03 22:32:26 UTC
view on stackexchange narkive permalink

Je n'ai pas plongé très loin dans la conception de votre filtre, mais le simple fait de regarder le code source amène plusieurs choses. Par exemple, ces lignes:

  aux1 = ((long) b0 * xn) << 1; aux2 = ((long) a1 * y) << 1; y = ((aux1 + aux2) >> 16);  

Le premier problème que je vois est le ((long) b0 * xn). J'ai rencontré des compilateurs qui compileraient cela de manière incorrecte comme ((long) (b0 * xn)), ce qui est entièrement faux. Juste pour être sûr, j'écrirais ceci comme (((long) b0) * ((long) xn)). Pour être sûr, c'est de la programmation paranoïaque, mais ...

Ensuite, quand vous faites le "<<1", ce n'est PAS la même chose que "* 2". Pour la plupart des choses, c'est proche, mais pas pour DSP. Cela a à voir avec la façon dont le MCU / DSP gère les conditions de débordement et signe les extensions, etc. Si vous devez vraiment faire un * 2, alors faites un * 2 et laissez le compilateur déterminer s'il pourrait remplacer <<1 à la place.

Le >> 16 est également problématique. Du haut de ma tête, je ne sais pas si cela va faire un changement logique ou arithmétique. Vous voulez un décalage arithmétique. Les décalages arithmétiques gèrent correctement le bit de signe où un décalage logique insère des zéros pour les nouveaux bits. De plus, vous pouvez économiser des bits de résolution en supprimant <<1 et en changeant le >> 16 en >> 15. Eh bien, et en changeant tout cela en multiplies et divisions normales.

Voici donc le code que j'utiliserais:

  aux1 = ((long) b0) * ((long ) xn); aux2 = ((long) a1) * ((long) y); y = (short) ((aux1 + aux2) / 32768);  

Maintenant, je ne ne prétendez pas que cela résoudra votre problème. Cela peut ou non, mais cela améliore votre code.

Cela n'a pas résolu mais cela a aidé à mieux comprendre le code et les "astuces" du compilateur. Le problème était avec les coefficients. Ce devrait être a1 * xn et b0 * y. Une erreur vraiment boiteuse en effet eheh Merci pour votre aide!
+1: techniquement, la norme C ne définit pas si >> est un décalage arithmétique ou logique ... ou du moins c'est ce que les gens de Microchip m'ont dit il y a quelques années quand j'ai signalé avoir des problèmes en utilisant >> pour power-of -2-divise en raison du manque d'extensions de signe.
markrages
2011-04-04 00:25:36 UTC
view on stackexchange narkive permalink

Je vous suggère de remplacer "short" par "int16_t" et "long" par "int32_t". Enregistrez "short", "int", "long" etc. pour les endroits où la taille n'a pas vraiment d'importance, comme les index de boucle.

Ensuite, l'algorithme fonctionnera de la même manière si vous compilez sur votre bureau ordinateur. Ce sera beaucoup plus facile à déboguer sur le bureau. Lorsqu'il fonctionne de manière satisfaisante sur le bureau, déplacez-le vers le microprocesseur. Pour rendre cela plus facile, intégrez la routine délicate dans son propre fichier. Vous voudrez probablement écrire une simple fonction main () pour le bureau qui exécute la routine et quitte zéro (pour le succès) ou différent de zéro (pour un échec).

Ensuite, intégrez la construction et l'exécution du test dans la construction système pour votre projet. Tout système de développement à considérer vous permettra de le faire, mais vous devrez peut-être lire le manuel. J'aime GNU Make.

Mieux encore, vous n'avez pas à définir vous-même ces typedefs si votre système a un : http://pubs.opengroup.org/onlinepubs/007904975/basedefs/stdint.h.html
Rocketmagnet
2012-04-21 01:49:58 UTC
view on stackexchange narkive permalink

David Kessner a mentionné le >> 16 comme n'étant pas fiable, ce qui est vrai. J'ai été mordu par ça avant, donc maintenant j'ai toujours une affirmation statique dans mon code qui vérifie que >> fonctionne comme je m'y attendais.

Il a suggéré une division réelle à la place. Mais voici comment faire une arithmétique correcte >> 16 s'il s'avère que la vôtre ne l'est pas.

  union {int16_t i16s [2]; uint16_t i16u [2]; int32_t i32s; uint32_t i32u;} union32; union32 x; x.i32s = -809041920; // diviser maintenant par 65536x.i16u [0] = x.i16u [1]; // Le changement se produit en une seule fois. // Maintenant nous faisons l'extension de signeif (x.i16u [0] & 0x8000) // Était-ce un nombre négatif? x.i16u [1] = 0xFFFF; // Ensuite, assurez-vous que le résultat final est toujours négatif, sinon x.i16u [1] = 0x0000; // Sinon, assurez-vous que le résultat final est toujours positif // Maintenant x.i32s = -12345  

Vérifiez que votre compilateur génère un bon code pour (x.i16u [0] & 0x8000) . Les compilateurs Microchip ne tirent pas toujours profit des instructions BTFSC du PIC dans des cas comme celui-ci. Surtout les compilateurs 8 bits. Parfois, je suis obligé d'écrire ceci dans asm quand j'ai vraiment besoin de la performance.

Faire le changement de cette façon est beaucoup plus rapide sur les PIC qui ne peuvent faire des changements qu'un bit à la fois.

+1 pour l'utilisation des typedefs intNN_t et uintNN_t, également l'approche union, que j'utilise toutes deux dans mes systèmes.
@JasonS - Merci. L'approche syndicale est ce qui sépare les développeurs embarqués des développeurs PC. Les gars de PC semblent n'avoir aucune idée du fonctionnement des nombres entiers.
Leon Heller
2011-04-03 21:10:12 UTC
view on stackexchange narkive permalink

Utiliser un dsPIC au lieu du PIC24 peut rendre les choses beaucoup plus faciles (ils sont compatibles avec les broches). Le compilateur dsPIC est livré avec une bibliothèque DSP qui comprend des filtres IIR et le matériel prend directement en charge l'arithmétique en virgule fixe.

Je dois utiliser ce MCU (PIC24F) car il doit être utilisé dans une application portable à faible consommation et dsPIC ne répond pas à cette exigence. Le filtre est simple, c'est un filtre du 1er ordre. Je suppose que mon problème est de convertir le format double-> point fixe. Cependant, en lisant le code, je pense que je fais la bonne chose, mais cela ne fonctionne pas ... Je me demande pourquoi.
"utilisé dans une application portable à faible puissance et dsPIC ne répond pas à cette exigence" <- ce genre de déclaration générale déclenche l'alarme dans ma tête. La consommation d'énergie dépend de l'application et la prise en charge matérielle signifie souvent une consommation d'énergie globale inférieure. Toute application à faible consommation d'énergie maintiendra le processeur endormi la plupart du temps, donc réduire le temps d'activation est le moyen de réduire la consommation d'énergie ...
est-ce une virgule fixe ou une virgule flottante? Le flottement serait un plus, mais vous n'avez pas besoin d'un DSP pour implémenter un filtre bi-quad. Vous avez bien plus besoin d'un logiciel de conception de filtres pour trouver les paramètres.
Kellenjb
2011-04-04 02:35:57 UTC
view on stackexchange narkive permalink

Il semble que vous ne donniez jamais de valeur à y avant de l'utiliser. Vous utilisez y dans le calcul pour aux2, ce qui est correct car c'est l'échantillon précédent filtré, mais vous n'avez aucune idée de ce que cela va commencer comme dans cette situation.

Je vous suggère de prendre un ADC échantillon avant d'entrer dans votre boucle while (1). Attribuez cette valeur à y, retardez 5 ms, puis entrez votre boucle. Cela permettra à votre filtre d'agir normalement.

Je vous recommande également d'aller de l'avant et de changer vos shorts pour qu'ils soient des variables plus longues. Sauf si vous manquez d'espace, vous aurez un code beaucoup plus simple si vous n'avez pas à taper cast dans chaque boucle.

davidcary
2011-04-29 06:33:01 UTC
view on stackexchange narkive permalink

De nombreux premiers MCU n'ont même pas d'instruction de multiplication, encore moins une division. Le filtre IIR avec la valeur magique avec K = 1/2 ^ 8 a été spécialement conçu pour ces MCU.

  // filtre implémente // y (n) = K * x (n) + y (n-1) * (1-K) // avec K = 1/2 ^ 8 .// basé sur des idées de // http : //techref.massmind.org/techref/microchip/math/filter.htm// AVERTISSEMENT: code non testé.// N'utilise aucune multiplication ni division.// Finalement, y converge vers une sorte de «moyenne» des valeurs x. moyenne courte; // variable globale // (...) while (1) {short xn = readADC (adc_ch); // espère que nous ne débordons pas la soustraction - // - peut-être utiliser l'arithmétique saturante ici? court aux1 = xn - moyenne; court aux2 = aux1 / 256; // le compilateur doit utiliser un décalage arithmétique, // ou peut-être un octet non aligné lu, pas une moyenne de division réelle + = aux2; delay_ms (5);}  


Ce Q&R a été automatiquement traduit de la langue anglaise.Le contenu original est disponible sur stackexchange, que nous remercions pour la licence cc by-sa 2.0 sous laquelle il est distribué.
Loading...