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

Comment créer un effet LED respirant à l'aide d'une onde sinusoïdale stockée dans le bloc RAM

J'ai remarqué que bon nombre des gadgets que j'ai achetés au cours des deux dernières années sont passés du clignotement des LED à la respiration des LED. La plupart des gadgets électroniques contiennent une LED d'état 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 lorsqu'il est temps de la recharger, et mon téléphone portable utilise la LED pour attirer mon attention pour une grande variété de raisons. Mais les LED ne clignotent pas comme elles le faisaient autrefois. Cela ressemble plus à un effet de pulsation analogique avec une intensité variable continue.

L'animation Gif ci-dessous montre ma souris Logitech utilisant cet effet pour indiquer qu'elle charge la batterie. J'appelle cet effet LED respiratoire parce que le schéma d'intensité lumineuse est similaire en vitesse et en accélération au cycle respiratoire humain. Cela semble si naturel parce que le cycle d'éclairage suit un modèle d'onde sinusoïdale.

Cet article est la suite 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 dents de scie et le module de réinitialisation que nous avons créés dans le tutoriel précédent. Cliquez sur le lien ci-dessous pour lire l'article sur PWM !

Comment créer un contrôleur PWM en VHDL

Stockage des valeurs d'onde sinusoïdale dans le bloc RAM

Bien qu'il soit possible de générer une onde sinusoïdale à l'aide de primitives de traitement numérique du signal (DSP) dans le FPGA, un moyen plus simple consiste à stocker les points d'échantillonnage dans le bloc RAM. 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 3 × 3 bits. L'entrée d'adresse et la sortie de données ont une largeur de trois bits, ce qui signifie qu'elles peuvent représenter une valeur entière sur la plage de 0 à 7. Nous pouvons stocker huit valeurs sinusoïdales dans la ROM, et la résolution des données est également de 0 à 7. .

La fonction sinus trigonométrique produit un nombre sur la plage [-1, 1] pour toute entrée d'angle, x . De plus, le cycle se répète lorsque x ≥ 2π. Par conséquent, il suffit de stocker uniquement les valeurs sinusoïdales de zéro et jusqu'à, mais sans inclure 2π. La valeur du sinus pour 2π est la même que celle du sinus pour zéro. L'illustration ci-dessus montre ce concept. Nous stockons les valeurs sinusoïdales de zéro à \frac{7\pi}{4}, qui est le dernier pas d'espacement régulier avant le cercle 2π complet.

La logique numérique ne peut pas représenter des valeurs réelles, comme les valeurs d'angle ou de sinus, avec une précision infinie. C'est le cas dans n'importe quel système informatique. Même lorsque vous utilisez des nombres à virgule flottante à double précision, ce n'est qu'une approximation. C'est ainsi que fonctionnent les nombres binaires, et notre ROM sinusoïdale 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 lorsque nous calculons le contenu ROM. La valeur sinusoïdale la plus basse possible de -1 correspond à la valeur de données 0, tandis que la valeur sinusoïdale la plus élevée possible de 1 se traduit par 2^{\mathit{data\_bits}-1}, comme le montre la formule ci-dessous.

\mathit{données} =\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{adresse} * 2\pi}{2^\mathit{adresse\_bits}}

Bien sûr, cette méthode ne vous donne pas un convertisseur angle-sinus universel. Si c'est ce que vous voulez, vous devrez utiliser des primitives logiques ou DSP supplémentaires 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 sous la forme d'un entier non signé est suffisante. Et comme nous le verrons dans la section suivante, c'est exactement ce dont nous avons besoin pour notre exemple de projet d'impulsion LED.

Module ROM sinusoïdal

Le module ROM sinusoïdal présenté dans cet article déduira de la RAM de bloc sur la plupart des architectures FPGA. Envisagez de faire correspondre les génériques de largeur et de 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 à l'exemple de projet Lattice si vous ne savez pas comment utiliser la ROM sinusoïdale pour un vrai projet FPGA.

Laissez votre email 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 sinusoïdal. Il contient deux entrées génériques qui vous permettent de spécifier la largeur et la profondeur du bloc RAM déduit. J'utilise des spécificateurs de plage sur les constantes pour éviter un débordement d'entier involontaire. Plus d'informations à ce sujet plus loin 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 réinitialisation car les primitives de RAM ne peuvent pas être réinitialisées. L'adresse l'entrée est l'endroit où nous attribuons l'angle mis à l'échelle (x ) et les données la sortie est l'endroit où la valeur sinusoïdale mise à l'échelle apparaîtra.

Déclarations de type

En haut de la région déclarative du fichier VHDL, nous déclarons un type et un sous-type pour notre objet de stockage ROM. La addr_range le sous-type est une plage d'entiers égale au nombre d'emplacements dans notre RAM, et le rom_type décrit un tableau 2D qui stockera toutes les valeurs de sinus.

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 encore déclarer le signal de stockage. Tout d'abord, nous devons définir la fonction qui produira les valeurs sinusoïdales, que nous pouvons utiliser pour transformer la RAM en ROM. Nous devons le déclarer au-dessus de la déclaration du signal afin que nous puissions utiliser la fonction pour attribuer une valeur initiale au signal de stockage ROM.

Notez que nous utilisons les addr_bits générique comme base pour définir addr_range . C'est la raison pour laquelle j'ai dû spécifier une valeur maximale de 30 pour addr_bits . Car pour des valeurs plus grandes, le 2**addr_bits - 1 le calcul débordera. Les entiers VHDL ont une longueur de 32 bits, bien que cela soit sur le point de changer avec VHDL-2019, qui utilisait des entiers 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 à prendre en charge VHDL-2019.

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

Le code ci-dessous montre le init_rom fonction qui génère les valeurs sinusoïdales. Il renvoie un rom_type objet, c'est pourquoi nous devons d'abord déclarer le type, 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 des valeurs sinusoïdales. Dans le sous-programme, nous utilisons une boucle for pour parcourir la plage d'adresses et, pour chaque emplacement de ROM, nous calculons la valeur sinusoïdale à l'aide des formules que j'ai décrites précédemment. Et à la fin, on retourne le rom_v variable qui contient maintenant tous les échantillons de sinus.

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

constant rom : rom_type := init_rom;

Sous le init_rom définition de la fonction, nous continuons à déclarer la rom objet comme une constante. Une ROM est simplement une RAM sur laquelle vous n'écrivez 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 indiqué dans le code ci-dessus.

Le processus ROM

La seule logique dans le fichier ROM sinusoïdal est le processus assez simple répertorié ci-dessous. Il décrit un bloc RAM 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 supérieur

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

Le schéma ci-dessous montre les connexions entre les sous-modules du module supérieur.

Le code ci-dessous montre la région déclarative du fichier VHDL supérieur. Dans la conception PWM de la semaine dernière, le duty_cycle l'objet était un alias pour les MSB du cnt compteur. Mais cela ne fonctionnerait pas maintenant car la sortie de la ROM sinusoïdale contrôlera le rapport cyclique, donc je l'ai remplacé par un signal réel. De plus, j'ai créé un nouvel alias avec le nom addr c'est-à-dire les MSB 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);

-- Use MSBs of counter for sine ROM address input
alias addr is cnt(cnt'high downto cnt'length - pwm_bits);

Vous pouvez voir comment instancier notre nouvelle ROM sinusoïdale dans le module supérieur de la liste ci-dessous. Nous définissons la largeur et la profondeur de la RAM pour suivre la longueur du compteur interne du module PWM. Les données la sortie de la ROM contrôle le duty_cycle entrée au module PWM. La valeur sur le duty_cycle le signal représentera un motif d'onde sinusoïdale lorsque nous lirons les emplacements de RAM l'un après l'autre.

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 à onde sinusoïdale

L'image ci-dessous montre la forme d'onde de la simulation du module supérieur dans ModelSim. J'ai changé la présentation du duty_cycle non signé signal au format analogique afin que nous puissions observer l'onde sinusoïdale.

C'est la led_5 sortie de niveau supérieur qui transporte le signal PWM, qui contrôle la LED externe. Nous pouvons voir que la sortie change rapidement lorsque le rapport cyclique augmente ou diminue. Mais lorsque le rapport cyclique est au sommet de l'onde sinusoïdale, led_5 est un « 1 » constant. Lorsque 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 email dans le formulaire ci-dessous pour recevoir les fichiers VHDL et les projets ModelSim et iCEcube2 !

Mise en œuvre de la respiration LED 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 essayez-le sur votre tableau si vous possédez un iCEstick !

La liste ci-dessous montre l'utilisation des ressources, telle que rapportée par le logiciel Synplify Pro fourni avec iCEcube2. Cela montre que la conception utilise une primitive de bloc RAM. C'est notre ROM sinusoïdale.

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 avoir routé le design dans iCEcube2, vous trouverez le .bin fichier dans le led_breathing_Implmnt\sbt\outputs\bitmap dossier à l'intérieur du Lattice_iCEcube2_proj répertoire du projet.

Vous pouvez utiliser le logiciel Lattice Diamond Programmer Standalone pour programmer le FPGA, comme indiqué dans le manuel d'utilisation 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 semble très naturel et la LED semble "respirer" si vous y mettez un peu d'imagination.

Réflexions finales

L'utilisation de blocs de RAM pour stocker des valeurs sinusoïdales précalculées est assez simple. Mais il existe quelques limitations qui rendent cette méthode adaptée uniquement aux ondes sinusoïdales avec des résolutions X et Y limitées.

La première raison qui me vient à l'esprit est la limite de 32 bits pour les valeurs entières dont j'ai parlé 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 avec lequel vous étendez l'adresse ROM, vous doublez l'utilisation de la RAM. Si vous avez besoin d'une grande précision sur l'axe X, il se peut qu'il n'y ait 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 de RAM native, l'outil de synthèse utilisera des RAM ou des LUT supplémentaires pour implémenter la ROM. Cela consommera plus de votre budget de ressources à mesure que vous augmenterez la précision 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 sortir 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. Il faudrait inverser le quadrant sinusoïdal pour les quatre permutations des axes X et Y. Ensuite, vous pouvez créer une onde sinusoïdale complète à partir du quadrant unique stocké dans le bloc RAM.

Malheureusement, il est difficile de faire en sorte que les quatre segments se rejoignent en douceur. Deux échantillons égaux dans les joints en haut et en bas de l'onde sinusoïdale déforment les données en créant des points plats sur l'onde sinusoïdale. L'introduction de bruit va à l'encontre de l'objectif de ne stocker que le quadrant pour améliorer la précision de l'onde sinusoïdale.


VHDL

  1. Comment créer un modèle CloudFormation à l'aide d'AWS
  2. Comment créer une expérience utilisateur sans friction
  3. Comment créer une liste de chaînes en VHDL
  4. Comment initialiser la RAM à partir d'un fichier à l'aide de TEXTIO
  5. Comment créer un banc d'essai d'auto-vérification
  6. Comment créer une liste chaînée en VHDL
  7. Comment créer un tableau d'objets en Java
  8. Comment les professionnels de la santé utilisent la fabrication numérique pour créer des modèles anatomiques de nouvelle génération
  9. Comment appeler un bloc fonction depuis un client OPC UA à l'aide d'un modèle d'information