Présentation

Le traitement numérique des signaux (DSP) est l'épine dorsale des systèmes intégrés modernes, permettant des opérations audio, vidéo, télémétrique et de communication en temps réel. L'écriture efficace du code C pour les tâches DSP a une incidence directe sur le débit, la consommation d'énergie et la latence du système. Contrairement au code général, les algorithmes DSP doivent exécuter dans des délais stricts tout en maximisant l'utilisation de ressources limitées en mémoire et en traitement.

Comprendre les principes fondamentaux du PSD en C

En C, le programmeur contrôle tous les aspects de la représentation et du flux des données, ce qui est critique pour l'exécution déterministe. Le code DSP fonctionne souvent sur des microcontrôleurs ou des processeurs numériques de signaux où le matériel est étroitement couplé – par exemple, des unités MAC (multipli-accumulables) ou des moteurs vectoriels SIMD. Une compréhension profonde de l'architecture cible de la hiérarchie de la mémoire, de l'instruction et des capacités périphériques est essentielle pour écrire un code C efficace.

Caractéristiques clés du code DSP:

  • Arithmétique répété: les boucles avec des opérations de multiplication-ajout dominent (p. ex., filtres FIR).
  • Contraintes en temps réel: chaque échantillon doit être traité dans une période d'échantillonnage.
  • La diffusion de données: les flux d'entrée/sortie en continu nécessitent un tampon efficace et une copie minimale.
  • De nombreux algorithmes DSP sont limités par la rapidité avec laquelle les données peuvent être déplacées, et non par des opérations arithmétiques.

Pour une référence fondamentale, voir Bases DSP des appareils analogiques.

Arithmétique à point fixe: Précision sans point flottant

De nombreux processeurs DSP ne disposent pas d'unités flottantes matérielles (FPU) ou ont des FPU plus lents. L'arithmétique fixe utilise des opérations entières avec un point radix implicite, fournissant des performances déterministes et une consommation d'énergie plus faible. La représentation la plus courante est Q notation: Qmnm bits sont une partie entière et n bits fractionnels. Par exemple, un format Q15 (1 bits de signe, 15 bits fractionnels) est omniprésent dans les DSP 16 bits.

Mise en œuvre des opérations à points fixes en C

L'ajout de points fixes est simple (ajout d'entiers simplement), mais la multiplication nécessite de régler le point radix. Pour la multiplication Q15, le produit de deux nombres Q15 nécessite un résultat intermédiaire 32 bits, puis vous faites un déplacement droit de 15 bits pour revenir à Q15. Exemple :

typedef int16_t q15_t;
q15_t q15_mul(q15_t a, q15_t b) {
 int32_t temp = (int32_t)a * (int32_t)b;
 return (q15_t)(temp >> 15);
}

Lorsque des accumulations se produisent (p. ex. dans les filtres), les bits de protection empêchent le débordement. Utilisez des accumulateurs 32 bits ou même 64 bits et des résultats saturés. Les bibliothèques à points fixes telles que ARM CMSIS-DSP offrent des fonctions à points fixes optimisées, y compris le filtrage, les transformations et les opérations de matrice.

Quand utiliser le point fixe vs le point flottant

Les processeurs modernes avec FPU (par exemple Cortex-M4/M7) peuvent exécuter des opérations flottantes aussi rapides que fixes. Utilisez un point flottant lorsque:

  • La gamme dynamique de l'algorithme est élevée (p. ex. filtres adaptatifs).
  • La maintenance du code est une priorité (moins l'analyse de la graduation).
  • Le matériel FPU est présent et le pipeline peut se superposer et se multiplier.

Sur les appareils à volume élevé sans FPU, le point fixe reste la norme pour les applications sensibles aux coûts.

Optimisation de l'accès à la mémoire pour DSP

Les algorithmes DSP traitent souvent de grandes séries de données séquentiellement. Les pannes de cache et les décrochages de bus peuvent tuer les performances.

  • Accès aux données linéaires:[ traversent des tableaux dans l'ordre contigu (ligne majeure en C). Éviter les schémas d'accès stridés à moins que l'algorithme n'exige (p. ex., les circuits de retour en bits FFT).
  • L'alignement des données: assure que les tableaux sont alignés sur les limites de la ligne de cache. Utilisez des attributs de compilateur comme ou des sections de mémoire spéciales.
  • Bundering: utilise un double tampon pour recouper les transferts DMA avec le traitement CPU. Bien que le CPU fonctionne sur un tampon, le prochain bloc d'échantillon est en cours de chargement.
  • Mot-clé restreint: utiliser C99=2 sur les pointeurs pour informer le compilateur que les pointeurs ne sont pas des alias, permettant la vectorisation et une meilleure programmation des instructions.

Par exemple, une simple fonction de filtre FIR doit être écrite avec "restrict" lorsque les tampons d'entrée et de sortie sont séparés:

void fir_lowpass(const int16_t * restrict x, int16_t * restrict y,
 const int16_t * restrict coeffs, int len, int order) {
 for (int i = 0; i < len; i++) {
 int32_t acc = 0;
 for (int j = 0; j < order; j++) {
 acc += (int32_t)x[i + j] * coeffs[j];
 }
 y[i] = (int16_t)(acc >> 15);
 }
}

Sélection et mise en œuvre efficaces de l'algorithme

La complexité algorithmique se traduit directement par le temps et la puissance d'exécution. Choisissez toujours l'algorithme le plus efficace pour la tâche:

  • Fast Fourier Transform (FFT):[ utiliser Cooley-Tukey radix-2 ou bifurcation pour la puissance de deux longueurs. Éviter le DFT naïf qui est O(N2). Précalculer les facteurs de la bifurcation et stocker dans ROM.
  • Filtres FIR: utiliser la décomposition polyphasée pour la décimation/interpolation; exploiter la symétrie pour les filtres linéaires pour réduire de moitié le nombre de multiplications.
  • ] utiliser la forme directe II transposée pour une meilleure stabilité numérique; utiliser des sections biquad en cascade (étapes du second ordre) pour réduire la sensibilité à la quantification des coefficients.
  • Convolution: pour les séquences longues, utiliser des méthodes de chevauchement-addition ou de chevauchement-save basées sur FFT plutôt que de convolution directe.

Voir la bibliothèque FFTW[ pour des références sur les techniques modernes de FFT (bien que non en C, ses principes sont largement copiés dans les bibliothèques DSP intégrées).

Caractéristiques du matériel de levier : Instructions SIMD et DSP

Presque tous les microcontrôleurs modernes comprennent des instructions SIMD (Single Instruction Multiple Data) ou DSP améliorées. Par exemple:

  • ARM Cortex-M4/M7: SIMD (SADD, SMUAD, etc.), arithmétique saturé et opérations fractionnelles (QADD, QSUB). Utilisez les fonctions intrinsèques CMSIS-DSP.
  • TI C6000 DSP: huit unités multipliées, double MAC et pipeline logicielle. Le TI DSP Optimization Guide fournit des techniques détaillées.
  • RISC-V avec extension P : les futures carottes auront des instructions semblables à celles du DSP.

Pour utiliser ces fonctionnalités en C, écrivez un code que le compilateur peut activer automatiquement (p. ex., des boucles simples sans dépendance) ou utilisez des fonctions intrinsèques du compilateur. Exemple en utilisant ARM CMSIS-DSP pour un filtre FIR :

#include "arm_math.h"
arm_fir_instance_f32 S;
float32_t firState[128];
arm_fir_init_f32(&S, numTaps, coeffs, firState, blockSize);
arm_fir_f32(&S, input, output, blockSize);

Ces bibliothèques sont adaptées à la main pour une performance maximale. Toujours profiler avant et après le passage des fonctions génériques C aux fonctions de bibliothèque.

Techniques d'optimisation de boucle

Comme les algorithmes DSP sont loop-havey, les optimisations au niveau de la boucle paient de grands dividendes :

  • Déroulement de boucle :[ manuellement ou avec pragmas compilateur (`#pragma dérouler N`) pour réduire les frais généraux de boucle et augmenter le parallélisme de niveau d'instruction.
  • Ligne de pipe :Loops restructuring de sorte que plusieurs itérations soient en vol simultanément. Certains compilateurs le font automatiquement; utilisez `-O3` et des drapeaux spécifiques à l'architecture.
  • Reduce ramification:[ remplace les conditions par des arithmétiques (p. ex. min/max avec ternaire), ou utilise des tables de recherche pour les fonctions non linéaires.
  • Utilisez des variables locales:[ stockez les données fréquemment consultées dans les registres en déclarant des variables dans la boucle ou en utilisant un indice `registrer`.
  • [FLT:][FLT:]][FLT:][FLT:][FLT:][FLT:]][FLT:][FLT:][FLT:]][FLT:][FLT:]][FLT:][FLT:][FLT:]][FLT:][FLT:][FLT:]][FLT:][FLT:][FLT:][FLT:][FLT:][FLT:][FLT:]][FLT:][FLT:][FLT:]][FLT]][FLT][FLT][FLT]][FLT][FLT]][FLT][FLT][F][F][F][FLT][F][

Précalcul des constantes et des tables de recherche

Les fonctions DSP telles que les valeurs trigonométriques, les coefficients et les facteurs de triburation doivent être précalculés hors ligne et stockés comme des tableaux constants dans ROM. Pour le démarrage en temps non réel, vous pouvez les calculer une fois et les réutiliser. Exemple : pour un FFT de 1024 points, précalculez les valeurs sinus/cosine pour chaque étape.

Les tables de recherche (LUTs) aident également à l'utilisation de fonctions comme racine carrée, exposant et log utilisées dans DSP (p. ex., dans le traitement de la parole).

Profilage et réglage

Aucune optimisation n'est complète sans mesure. Utilisez ces techniques pour identifier les goulets d'étranglement:

  • Profilage précis du cycle de cycle :[ utiliser des compteurs de cycle embarqués (p. ex., DWT CYCCNT sur Cortex-M) pour mesurer la durée de la fonction.
  • Profilage statistique: compteur de programme d'échantillon (PC) pour voir quelles fonctions consomment du temps CPU.
  • Profilage de mémoire: utilise des outils pour surveiller les pannes de cache (si disponibles) et les transactions de bus.
  • ][FLT][FLT][FLT]][FLT][FLT]][FLT][FLT][FLT][FLT]][FLT][FLT][FLT]][FLT][FLT][FLT]][FLT][FLT]][FLT][FLT]][FLT]][FLT]][FLT]][FLT][FLT]][FACT][FLT][FLT][FLT][FLT][FLT]][FLT][FLT][FLT]][FLT]][FLT][FLT][FLT]][FLT][FLT]][FLT][FLT][FLT]][FACT][FACT][FACT][

Il est souvent le plus grand gain provient de l'amélioration des modèles d'accès à la mémoire plutôt que de l'ajustement de l'arithmétique.

Résumé pratique: Tout mettre en oeuvre

La rédaction de codes efficaces du PSD en C nécessite une approche holistique :

  • Choisissez la bonne représentation des données (point fixe vs point flottant).
  • Concevoir des structures de données pour l'accès et l'alignement séquentielle.
  • Sélectionnez des algorithmes à faible complexité (FFT, polyphase).
  • Utilisez les bibliothèques DSP des fournisseurs lorsque disponibles.
  • Déroulez les boucles et réduisez les ramifications.
  • Précalculer les constantes en ROM.
  • Profiler sans relâche et laisser le compilateur aider.

En appliquant ces principes, les développeurs peuvent obtenir un débit de traitement de signal comparable à un montage à la main tout en conservant la portabilité et la maintenance de C. Le résultat est des systèmes DSP fiables en temps réel qui répondent aux exigences des produits embarqués modernes – des appareils auditifs aux stations de base 5G.