Fabrication industrielle
Internet des objets industriel | Matériaux industriels | Entretien et réparation d'équipement | Programmation industrielle |
home  MfgRobots >> Fabrication industrielle >  >> Industrial programming >> VHDL

Créer un effet LED respirant avec une onde sinusoïdale depuis la RAM bloc

J'ai remarqué que beaucoup de gadgets que j'ai achetés ces dernières années sont passés du clignotement LED à la LED respirante. La plupart des gadgets électroniques contiennent une LED de statut dont le comportement donne des indications sur ce qui se passe à l'intérieur de l'appareil.

Ma brosse à dents électrique fait clignoter une LED quand il est temps de la recharger, et mon téléphone mobile utilise la LED pour attirer mon attention pour une grande variété de raisons. Mais les LED ne clignotent plus comme dans le bon vieux temps. C'est plus comme un effet de pulsation analogique avec une intensité variant continuellement.

L'animation GIF ci-dessous montre ma souris Logitech utilisant cet effet pour indiquer qu'elle charge la batterie. J'appelle cet effet LED respirante car le motif d'intensité lumineuse est similaire en vitesse et accélération au cycle de respiration humain. Cela paraît si naturel car le cycle d'illumination suit un motif d'onde sinusoïdale.

Créer un effet LED respirant avec une onde sinusoïdale depuis la RAM bloc

Cet article est la continuation du billet de blog de la semaine dernière sur la modulation de largeur d'impulsion (PWM). Aujourd'hui, nous allons utiliser le module PWM, le compteur en dent de scie, et le module de reset que nous avons créés dans le tutoriel précédent. Cliquez sur le lien ci-dessous pour lire l'article sur le PWM !

Comment créer un contrôleur PWM en VHDL

Stocker les valeurs d'onde sinusoïdale dans la RAM bloc

Bien qu'il soit possible de générer une onde sinusoïdale en utilisant les primitives de traitement de signal numérique (DSP) dans le FPGA, une méthode plus simple consiste à stocker les points d'échantillonnage dans la RAM bloc. Au lieu de calculer les valeurs pendant l'exécution, nous calculons une série de valeurs sinusoïdales pendant la synthèse et créons une mémoire morte (ROM) pour les stocker.

Considérez l'exemple minimal ci-dessous, qui montre comment stocker une onde sinusoïdale complète dans une ROM de 3×3 bits. L'entrée d'adresse et la sortie de données sont toutes deux larges de 3 bits, ce qui signifie qu'elles peuvent représenter une valeur entière sur l'intervalle de 0 à 7. Nous pouvons stocker huit valeurs sinusoïdales dans la ROM, et la résolution des données est aussi de 0 à 7.

Créer un effet LED respirant avec une onde sinusoïdale depuis la RAM bloc

La fonction trigonométrique sinus produit un nombre dans l'intervalle [-1, 1] pour toute entrée d'angle x. De plus, le cycle se répète quand x ≥ 2π. Par conséquent, il suffit de stocker seulement les valeurs sinusoïdales de zéro jusqu'à, mais sans inclure 2π. La valeur du sinus pour 2π est la même que pour zéro. L'illustration ci-dessus montre ce concept. Nous stockons les valeurs sinusoïdales de zéro à 7π/4, qui est le dernier pas uniformément espacé avant le cercle complet de 2π.

La logique numérique ne peut pas représenter des valeurs réelles, comme l'angle ou les valeurs sinusoïdales, avec une précision infinie. C'est le cas dans tout système informatique. Même en utilisant des nombres à virgule flottante à double précision, ce n'est qu'une approximation. C'est ainsi que fonctionnent les nombres binaires, et notre ROM sinus n'est pas différente.

Pour tirer le meilleur parti des bits de données disponibles, nous ajoutons un décalage et une échelle aux valeurs sinusoïdales quand nous calculons le contenu de la ROM. La plus basse valeur sinusoïdale possible de -1 correspond à la valeur de données 0, tandis que la plus haute valeur sinusoïdale possible de 1 se traduit par 2^{data_bits-1}, comme le montre la formule ci-dessous.

\mathit{data} = \mathit{Round}\left(\frac{(1 + \sin \mathit{addr}) * (2^\mathit{data\_bits} - 1)}{2}\right)

Pour traduire une adresse ROM en un angle, x, nous pouvons utiliser la formule suivante :

x = \frac{\mathit{addr} * 2\pi}{2^\mathit{addr\_bits}}

Bien sûr, cette méthode ne vous donne pas un convertisseur universel d'angle en valeur sinusoïdale. Si c'est ce que vous voulez, vous devrez utiliser une logique additionnelle ou des primitives DSP pour mettre à l'échelle l'entrée d'adresse et la sortie de données. Mais pour de nombreuses applications, une onde sinusoïdale représentée comme un entier non signé est suffisante. Et comme nous le verrons dans la prochaine section, c'est exactement ce dont nous avons besoin pour notre projet de LED pulsante.

Module ROM sinus

Le module ROM sinus présenté dans cet article inférera la RAM bloc sur la plupart des architectures FPGA. Pensez à faire correspondre les génériques de largeur et profondeur aux primitives de mémoire de votre FPGA cible. Cela vous donnera la meilleure utilisation des ressources. Vous pouvez toujours vous référer au projet exemple Lattice si vous n'êtes pas sûr de comment utiliser la ROM sinus pour un vrai projet FPGA.

Laissez votre e-mail dans le formulaire ci-dessous pour télécharger les fichiers VHDL et les projets ModelSim / iCEcube2 !

L'entité

Le code ci-dessous montre l'entité du module ROM sinus. Il contient deux entrées génériques qui vous permettent de spécifier la largeur et la profondeur de la RAM bloc inférée. J'utilise des spécificateurs de plage sur les constantes pour prévenir les débordements d'entier involontaires. Plus sur cela plus tard dans cet article.

 entity sine_rom is generic ( addr_bits : integer range 1 to 30; data_bits : integer range 1 to 31 ); port ( clk : in std_logic; addr : in unsigned(addr_bits - 1 downto 0); data : out unsigned(data_bits - 1 downto 0) ); end sine_rom; 

La déclaration de port a une entrée d'horloge, mais pas de reset car les primitives RAM ne peuvent pas avoir de reset. L'entrée addr est où nous assignons la valeur d'angle mise à l'échelle (x), et la sortie data est où la valeur sinusoïdale mise à l'échelle apparaîtra.

Déclarations de types

Au sommet de la région déclarative du fichier VHDL, nous déclarons un type et un sous-type pour notre objet de stockage ROM. Le sous-type addr_range est un intervalle d'entiers égal au nombre de slots dans notre RAM, et le rom_type décrit un tableau 2D qui stockera toutes les valeurs sinusoïdales.

 subtype addr_range is integer range 0 to 2**addr_bits - 1; type rom_type is array (addr_range) of unsigned(data_bits - 1 downto 0); 

Cependant, nous n'allons pas déclarer le signal de stockage tout de suite. D'abord, nous devons définir la fonction qui produira les valeurs sinusoïdales, que nous pourrons utiliser pour transformer la RAM en ROM. Nous devons la déclarer au-dessus de la déclaration de signal pour pouvoir utiliser la fonction pour assigner une valeur initiale au signal de stockage ROM.

Notez que nous utilisons le générique addr_bits comme base pour définir addr_range. C'est la raison pour laquelle j'ai dû spécifier une valeur max de 30 pour addr_bits. Car pour des valeurs plus grandes, le calcul 2**addr_bits - 1 débordera. Les entiers VHDL font 32 bits de long, bien que cela soit sur le point de changer avec VHDL-2019, qui utilise des entiers de 64 bits. Mais pour l'instant, nous devons vivre avec cette limitation lors de l'utilisation d'entiers en VHDL jusqu'à ce que les outils commencent à supporter VHDL-2019.

Fonction de génération des valeurs sinusoïdales

Le code ci-dessous montre la fonction init_rom qui génère les valeurs sinusoïdales. Elle retourne un objet rom_type, c'est pourquoi nous devons déclarer le type d'abord, puis la fonction, et enfin la constante ROM. Ils dépendent les uns des autres dans cet ordre exact.

 function init_rom return rom_type is variable rom_v : rom_type; variable angle : real; variable sin_scaled : real; begin for i in addr_range loop angle := real(i) * ((2.0 * MATH_PI) / 2.0**addr_bits); sin_scaled := (1.0 + sin(angle)) * (2.0**data_bits - 1.0) / 2.0; rom_v(i) := to_unsigned(integer(round(sin_scaled)), data_bits); end loop; return rom_v; end init_rom; 

La fonction utilise quelques variables de commodité, dont rom_v, une copie locale du tableau que nous remplissons avec les valeurs sinusoïdales. À l'intérieur du sous-programme, nous utilisons une boucle for pour itérer sur l'intervalle d'adresses, et pour chaque slot de ROM, nous calculons la valeur sinusoïdale en utilisant les formules que j'ai décrites plus tôt. Et à la fin, nous retournons la variable rom_v qui contient maintenant tous les échantillons sinusoïdaux.

La conversion en entier sur la dernière ligne de la boucle for est la raison pour laquelle j'ai dû restreindre le générique data_bits à 31 bits. Il débordera pour toute longueur de bits plus grande.

 constant rom : rom_type := init_rom; 

Sous la définition de la fonction init_rom, nous déclarons l'objet rom comme une constante. Une ROM est simplement une RAM dans laquelle on n'écrit jamais, donc c'est parfaitement bien. Et maintenant il est temps d'utiliser notre fonction. Nous appelons init_rom pour générer les valeurs initiales, comme montré dans le code ci-dessus.

Le processus ROM

La seule logique dans le fichier ROM sinus est le processus plutôt simple listé ci-dessous. Il décrit une RAM bloc avec un seul port de lecture.

 ROM_PROC : process(clk) begin if rising_edge(clk) then data <= rom(to_integer(addr)); end if; end process; 

Module top

Cette conception est une continuation du projet PWM que j'ai présenté dans mon billet de blog précédent. Elle a un module de reset, un module générateur PWM, et un module compteur libre de cycles d'horloge (compteur en dent de scie). Reportez-vous à l'article de la semaine dernière pour voir comment ces modules fonctionnent.

Le diagramme ci-dessous montre les connexions entre les sous-modules dans le module top.

Créer un effet LED respirant avec une onde sinusoïdale depuis la RAM bloc

Le code ci-dessous montre la région déclarative du fichier VHDL top. Dans la conception PWM de la semaine dernière, l'objet duty_cycle était un alias pour les MSBs du compteur cnt. Mais cela ne marcherait pas maintenant car la sortie de la ROM sinus contrôlera le cycle de travail, donc je l'ai remplacé par un signal réel. De plus, j'ai créé un nouvel alias nommé addr qui est les MSBs du compteur. Nous l'utiliserons comme entrée d'adresse pour la ROM.

 signal rst : std_logic; signal cnt : unsigned(cnt_bits - 1 downto 0); signal pwm_out : std_logic; signal duty_cycle : unsigned(pwm_bits - 1 downto 0); -- Utiliser les MSBs du compteur pour l'entrée d'adresse de la ROM sinus alias addr is cnt(cnt'high downto cnt'length - pwm_bits); 

Vous pouvez voir comment instancier notre nouvelle ROM sinus dans le module top dans la liste ci-dessous. Nous réglons la largeur et la profondeur de la RAM pour suivre la longueur du compteur interne du module PWM. La sortie data de la ROM contrôle l'entrée duty_cycle du module PWM. La valeur sur le signal duty_cycle dépeindra un motif d'onde sinusoïdale quand nous lirons les slots de RAM les uns après les autres.

 SINE_ROM : entity work.sine_rom(rtl) generic map ( data_bits => pwm_bits, addr_bits => pwm_bits ) port map ( clk => clk, addr => addr, data => duty_cycle ); 

Simulation de la ROM d'onde sinusoïdale

L'image ci-dessous montre la forme d'onde de la simulation du module top dans ModelSim. J'ai changé la présentation du signal unsigned duty_cycle en format analogique pour que nous puissions observer l'onde sinusoïdale.

Créer un effet LED respirant avec une onde sinusoïdale depuis la RAM bloc

C'est la sortie de niveau supérieur led_5 qui porte le signal PWM, qui contrôle la LED externe. Nous pouvons voir que la sortie change rapidement quand le cycle de travail monte ou descend. Mais quand le cycle de travail est au sommet de l'onde sinusoïdale, led_5 est un '1' stable. Quand l'onde est au bas de la pente, la sortie reste brièvement à '0'.

Vous voulez l'essayer sur votre ordinateur personnel ? Entrez votre adresse e-mail dans le formulaire ci-dessous pour recevoir les fichiers VHDL et les projets ModelSim et iCEcube2 !

Implémentation de la LED respirante sur le FPGA

J'ai utilisé le logiciel Lattice iCEcube2 pour implémenter la conception sur la carte FPGA iCEstick. Utilisez le formulaire ci-dessus pour télécharger le projet et l'essayer sur votre carte si vous possédez un iCEstick !

La liste ci-dessous montre l'utilisation des ressources, telle que rapportée par le logiciel Synplify Pro qui accompagne iCEcube2. Elle montre que la conception utilise une primitive de RAM bloc. C'est notre ROM sinus.

 Resource Usage Report for led_breathing Mapping to part: ice40hx1ktq144 Cell usage: GND 4 uses SB_CARRY 31 uses SB_DFF 5 uses SB_DFFSR 39 uses SB_GB 1 use SB_RAM256x16 1 use VCC 4 uses SB_LUT4 65 uses I/O ports: 7 I/O primitives: 7 SB_GB_IO 1 use SB_IO 6 uses I/O Register bits: 0 Register bits not including I/Os: 44 (3%) RAM/ROM usage summary Block Rams : 1 of 16 (6%) Total load per clock: led_breathing|clk: 1 @S |Mapping Summary: Total LUTs: 65 (5%) 

Après le routage de la conception dans iCEcube2, vous trouverez le fichier .bin dans le dossier led_breathing_Implmnt\sbt\outputs\bitmap à l'intérieur du répertoire du projet Lattice_iCEcube2_proj.

Vous pouvez utiliser le logiciel Lattice Diamond Programmer Standalone pour programmer le FPGA, comme indiqué dans le manuel utilisateur de l'iCEstick. C'est ce que j'ai fait, et l'animation GIF ci-dessous montre le résultat. L'intensité lumineuse de la LED oscille avec un motif d'onde sinusoïdale. Cela paraît très naturel, et la LED semble « respirer » si vous y mettez un peu d'imagination.

Créer un effet LED respirant avec une onde sinusoïdale depuis la RAM bloc

Réflexions finales

Utiliser la RAM bloc pour stocker des valeurs sinusoïdales précalculées est assez simple. Mais il y a quelques limitations qui rendent cette méthode adaptée seulement aux ondes sinusoïdales avec des résolutions X et Y limitées.

La première raison qui vient à l'esprit est la limite de 32 bits pour les valeurs entières que j'ai discutée plus tôt. Je suis sûr que vous pouvez surmonter ce problème en calculant la valeur sinusoïdale plus intelligemment, mais ce n'est pas tout.

Pour chaque bit que vous étendez l'adresse ROM, vous doublez l'utilisation de la RAM. Si vous avez besoin de haute précision sur l'axe X, il n'y aura peut-être pas assez de RAM, même sur un FPGA plus grand.

Si le nombre de bits utilisés pour les valeurs sinusoïdales de l'axe Y dépasse la profondeur native de la RAM, l'outil de synthèse utilisera des RAMs supplémentaires ou des LUTs pour implémenter la ROM. Cela mangera plus de votre budget de ressources à mesure que vous augmentez la précision en Y.

En théorie, nous n'avons besoin de stocker qu'un quadrant de l'onde sinusoïdale. Par conséquent, vous pourriez vous en tirer avec un quart de l'utilisation de la RAM si vous utilisiez une machine à états finis (FSM) pour contrôler la lecture de la ROM. Elle devrait inverser le quadrant sinusoïdal pour les quatre permutations des axes X et Y. Alors, vous pourriez construire une onde sinusoïdale complète à partir du seul quadrant stocké dans la RAM bloc.

Malheureusement, il est difficile de faire joindre les quatre segments en douceur. Deux échantillons égaux dans les jonctions au sommet et au bas de l'onde sinusoïdale déforment les données en créant des zones plates sur l'onde sinusoïdale. Introduire du bruit contredit le but de ne stocker que le quadrant pour améliorer la précision de l'onde sinusoïdale.


VHDL

  1. Construire votre premier template AWS CloudFormation — Guide pratique
  2. Créer des expériences utilisateur sans friction : un guide de conception UX
  3. Créer des listes de chaînes en VHDL : Bonnes pratiques et exemple
  4. Initialiser la RAM bloc FPGA depuis des fichiers texte avec la bibliothèque TEXTIO de VHDL
  5. Comment construire un testbench auto-vérifiant en VHDL - Guide pratique
  6. Implémentation d'une liste chainée dynamique en VHDL avec types protégés et pointeurs d'accès
  7. Créer un tableau d'objets en Java : Guide étape par étape
  8. La fabrication numérique révolutionne la modélisation anatomique en médecine moderne
  9. Invoquer des blocs de fonction PLC via clients OPC UA : guide pratique