Skyduino:~#
Articles
C/C++, programmation, projet

[ATtiny] Générateur de signaux DDS (DAC software)

Bonjour tout le monde !

Aujourd’hui je continue sur ma lancée avec un nouvelle article sur la conversion numérique -> analogique.
Dans mon dernier article je vous avez montrez comment utiliser un MCP4725 pour générer un signal analogique.

Aujourd’hui on passent au cran au dessus en générant un signal analogique SANS aucun CI spécialisé !

MER IL EST FOU ! Comment un tel exploit est-il possible !?

Le principe utilisé dans cet article ce résume en trois lettres : DDS aka « Direct digital synthesizer ».

Selon wikipedia voila ce que DDS signifie :
Direct Digital Synthesizer (DDS) is a type of frequency synthesizer used for creating arbitrary waveforms from a single, fixed-frequency reference clock.

En gros en utilisant une fréquence de référence fixe, on peut générer un signal totalement arbitraire selon nos envies.

Ok mais … COMMENT !?

Le principe est simple, beaucoup de micro-contrôleurs possède un générateur de signal PWM.
Parmis ces micro-contrôleurs certain possède en plus une option « Fast PWM ».

En général un signal PWM tourne autour des 500Hz … ce qui est trés insuffisant pour faire de la DDS !
Mais ceux possédant l’option « Fast PWM » peuvent, moyennant un « 1 » bien placé dans le registre adéquat, générer un signal PWM à la fréquence d’horloge du micro-contrôleur.

Dans mon montage j’utilise un ATtiny85 tournant à 16MHz via l’oscillateur RC interne.
Cela signifie que je peut générer des signaux PWM à 16MHz ! Et là c’est tiptop pour faire de la DDS !

Le principe :

Un signal PWM est un signal de fréquence fixe mais de duty (rapport temps haut / temps bas) variable.

Maintenant imaginons que l’on passe ce signal de trés haute fréquence au travers d’un filtre RC.
Et bien en sortie on obtiendra un signal analogique pure égale à la moyenne (enfin à l’intégrale … ne chipotons pas) du signal PWM !

Il suffit alors de modifier le duty du signal pwm pour faire varier la valeur de la tension analogique en sortie du filtre.

Bon je ne vais pas vous faire un cours complet sur la PWM (Pulse width modulation), la PPM (pulse position modulation), et sur les filtres 😉

Abat la théorie, vive la pratique !

Tout d’abord commençons avec un bon schéma à l’ancienne :

Puis continuons sur notre lancée avec un montage sur breadboard :

Sans oublier de garder dans un coin un programmateur d’AVR (ici un AVR POCKET PROGRAMMER de sparkfun) 😉

Et maintenant un peu de code …

Le code fonctionne suivant un principe simple mais puissant.

Un premier Timer génère le signal « Fast PWM » sur la broche PB1.
Un second timer lui génère une interruption à un intervalle de temps donnée, permettant de charger de manière continu les valeurs du signal de sortie.

En se basant sur ce modèle on peut faire à peu prés n’importe quoi :
– générateur de signaux,
– lecteur de musique au format PCM,
– etc …

Voici le code de main.c permettant (au choix) soit de générer des signaux « classique », sinus, carré, triangle, … soit de lire un fichier audio PCM (« It’s working » durée ~1s) :
Edit 28/09 : Le code source et le .hex pré-compilé sont désormais disponible sur mon github :
https://github.com/skywodd/attiny_software_dac_single

Le résultat :

Admirez par vous même, la qualité est au rendez vous 😉

sinusoïde :

Dent de scie :

Triangle :

Carré (ou rectangle pour les puristes) :

Pseudo bruit :

Pas mal pour un ATtiny85, une résistance et un condensateur 😉

Remarque : Le fichier LibreOffice Calc qui m’as permis de générer les séries de valeurs est inclut dans le zip 😉

Enjoy 🙂

Ps: Dans le prochain article je vous montreraient comment allez encore plus loin avec une version 2 voies générant un image XY sur un oscilloscope 😉

Advertisements

Discussion

23 réflexions sur “[ATtiny] Générateur de signaux DDS (DAC software)

  1. Là je suis largué !
    SSCAPA

    Publié par sscapa | 14 juin 2012, 16 h 41 min
  2. Je ne comprend pas bien la fonction soft_dac_sampling_frequency( uint32_t frequency ) car toutes les conditions quelle contient sont identiques! if(ocr > 255) {…

    Publié par Maurin DONNEAUD | 18 juin 2012, 18 h 06 min
    • Les conditions sont identique MAIS la variables ocr est différente pour chaque calcul !
      ocr = F_CPU / frequency / xxxxxxx - 1;

      J’aurai pu faire une boucle, ça aurait était beaucoup plus propre mais j’avais la flemme 😉

      EDIT: La boucle for correspondante à la série de if :

      for(prescaler = 1; prescaler < 16; ++prescaler) {
        ocr = F_CPU / frequency / pow(2, prescaler - 1) - 1;
        if(ocr <= 255) break;
        ocr = 255;
      }
      

      Publié par skywodd | 18 juin 2012, 18 h 12 min
      • J’aimerais bien avoir un peux plus d’explications sur cette fonction que je trouve un peu trapue. Si je ne me trompe pas, elle permet de sélectionner automatiquement le prescaler le plus adapté à la fréquence que tu souhaite obtenir pour driver l’interruption du Timer/Counter1, ensuite tu ajuster la valeur max du conteur (OCR1A) pour obtenir la bonne fréquence, c’est bien ça !?

        Publié par Maurin DONNEAUD | 18 juin 2012, 19 h 51 min
  3. @Maurin DONNEAUD Cette fonction permet de calculer le prescaler minimum nécéssaire pour générer une interruption à la fréquence voulu.

    Si le calcul du registre OCR (8 bits) dépasse 255 cela signifie que le prescaler est insuffisant, la fonction passe donc en revue chaque prescaler jusqu’à trouver un prescaler suffisant pour que OCR ne dépasse pas 255.

    Le couple prescaler + registre OCR permet par la suite d’avoir un interruption à la fréquence voulu.
    Tout cela dépend bien sur de la fréquence du microcontroleur F_CPU connu à la compilation.

    Cf http://maxembedded.wordpress.com/2011/06/22/introduction-to-avr-timers/ pour la partie théorique du calcul.

    Publié par skywodd | 18 juin 2012, 20 h 02 min
  4. Peux tu me donner quelques conseils pour implémenter une enveloppe de volume ADSR.

    Publié par Maurin DONNEAUD | 28 juillet 2012, 13 h 44 min
    • J’ai utilisé une enveloppe ADSR seulement une fois, pour ce programme justement.
      Le principe est simple mais tout est une question de calcul et j’avais un peu la flemme de me (re)pencher sur mon code.

      Dans le principe c’est juste 4 tables en PROGMEM qui représente les plusieurs « parties » de ton son en fonction du temps.

      ADSR = 4 parties :
      Attack -> début du son
      Decay -> stabilisation du son
      Sustain -> la partie du son qui peut être généré indéfiniment
      Release -> fin du son

      Voir ce lien pour un exemple : http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=118933

      Publié par skywodd | 28 juillet 2012, 15 h 08 min
  5. dans la fonction soft_dac_sampling_frequency( uint32_t frequency ) je ne comprends pas à quoi sert cette ligne :
    if(ocr > 255) ocr = 255;
    la portion de code qui précéde n’est censée retenir (grace au break;) uniquement les valeurs OCR<=255 ?

    et cette syntaxe :
    TCCR1 = (TCCR1 & ~((1 << CS13) | (1 << CS12) | (1 << CS11) | (1 << CS10))) | prescaler;
    je suppose que cela permet de changer les valeurs du prescaler à la volée mais je ne comprends pas trop
    la syntaxe.

    merci de votre aide

    Publié par fobb | 25 février 2013, 17 h 43 min
    • >> la portion de code qui précède n’est censée retenir (grâce au break;) uniquement les valeurs OCR> je suppose que cela permet de changer les valeurs du prescaler à la volée mais je ne comprends pas trop
      la syntaxe.

      Cette ligne permet de fixer le prescaler tout en ayant une configuration fixe pour le reste de bits.

      Il faut ce rappeler que les bits autre que ceux du prescaler ont du être fixé par la ligne suivante de soft_dac_initialize() :
      TCCR1 = (1 << CTC1) | (0 << PWM1A) | (0 << COM1A1) | (0 << COM1A0) | (0 << CS13) | (0 << CS12) | (0 << CS11) | (0 << CS10);

      Si soft_dac_initialize() n'as jamais été appelé le ET bit à bit travaillera sur un registre TCCR1 à 0 (valeur au reset) ce qui gardera le timer éteint et évitera tout problème, tout en facilitant le debug.

      Publié par skywodd | 25 février 2013, 20 h 52 min
      • et pour ce qui est de if(ocr > 255) ocr = 255; ? pourquoi mettre cette condition puisque vous ne retenez (avec le break;)que les valeurs d’ ocr <= 255 ?

        Publié par fobb | 26 février 2013, 16 h 19 min
      • C’est pour gérer le cas où même le prescaler maximum ne suffit pas, mais comme je fait un « ocr & 0xFF » juste âpres c’est pas vraiment utile.

        Publié par skywodd | 26 février 2013, 16 h 48 min
  6. -> »C’est pour gérer le cas où même le prescaler maximum ne suffit pas (…) »
    concretement c’est pour éviter de sortir des valeurs minimum et maximum en terme de frequence ? donc pour les cas où la frequence demandée de sampling rate est trop basse ou trop haute, c’est bien cela ?

    je suis un parfait newbie en programmation AVR. j’ai du mal à comprendre la différence entre écrire
    simplement OCR1A = ocr;
    et OCR1A = ocr & 0xFF;
    pourquoi faire un & avec la valeur max 255 ? qu’est ce que cela change ?

    Publié par fobb | 26 février 2013, 23 h 04 min
  7. Bonjour,

    cet article date un peut mais je le trouve passionnant merci beaucoup !!!
    j’aimerais pouvoir reproduire le système uniquement pour la génération de signaux.
    J’ai tout le matériel mais je suis débutant avec arduino et je ne comprend pas très bien comment utiliser tout les codes que tu donne ici https://github.com/skywodd/attiny_software_dac_single
    le main.c ok mais tout les autres je ne sais pas ou les placer.

    Pourrais tu me donner un peut plus d’infos ?

    Merci d’avance,
    Cordialement,

    Siera

    Publié par siera | 16 novembre 2013, 17 h 00 min
    • Tu n’as besoin que de soft_dac.c et soft_dac.h si tu veut que la partie DAC software, tout le reste c’est du code d’exemple (et un makefile pour compiler l’exemple).

      PS: c’est pas du code Arduino, c’est du code AVR-C bas niveau compatible uniquement ATtiny25/45/85.
      Ça peut ce compiler avec l’ide Arduino mais en soit il n’y a rien « d’arduino » dans ce code.

      Publié par skywodd | 18 novembre 2013, 20 h 18 min
  8. Bonsoir,

    merci bien de ta réponse !

    Je sais utiliser mon arduino pour programmer un attiny 85, mais après plusieurs tentatives je n’arrive pas à générer des signaux.

    Pourrais tu m’expliquer en détail comment utiliser tes codes et comment changer le type et la fréquence du signal de sortie ?

    Merci beaucoup d’avance,

    cordialement,

    Siera.

    Publié par siera | 26 novembre 2013, 22 h 17 min
    • Si tu veut utiliser mon code tel-quel :
      https://github.com/skywodd/attiny_software_dac_single/blob/master/main.c

      Changer la fréquence : ligne 88

      soft_dac_sampling_frequency(8000UL);
      

      Tu remplaces 8000UL par la fréquence voulue * 361.
      Maximum en sortie : 100Hz, peut être un peu plus à voir.

      Changer le type : tu vires le contenu du for(;;) dans main() et ligne 44:

      volatile uint16_t pcm_samples = (uint16_t)wave_sinus;
      

      Tu remplaces wave_sinus par ce que tu veut (voir dans wavetables.h).

      —–

      Sinon si tu veut faire de la vrai DDS qui peut monter à 4KHz voir plus (parce que mon code n’est en réalité pas de la DDS, juste un convertisseur N/A software) :
      https://github.com/skywodd/Cheaptune

      Les classes StandardOscillator, Waveform et les divers implémentations de Waveform sont celles qui t’intéresses.
      Pour mixer ces classes avec mon code « soft_dac » il te faut instancier la classe Waveform de ton choix, la passer à une instance de StandardOscillator et appeler getSample() dans l’interruption du code soft_dac de base pour récupérer un échantillon de son.

      Voici un exemple qui devrait marcher :

      /* Includes */
      #include <avr/interrupt.h>
      #include <stdint.h>
      #include "soft_dac.h"
      
      #include "defines.h"
      #include "StandardOscillator.h"
      #include "SinusWaveform.h"
      
      CheapTune::SinusWaveform waveform; // Sinus
      CheapTune::StandardOscillator osc(&waveform, 440); // 440Hz
      
      /**
       * Sampling ISR
       */
      ISR(TIMER1_COMPA_vect) {
      
        /* output current sample */
        soft_dac_output((osc.getSample() / 256) + 127);
        // De base getSample() retourne un entier signé sur 16 bits, /256 + 127 transforme cet entier en 8 bits non signé
        
        /* Re-arm sampling timer */
        soft_dac_sampling_reset();
      }
      
      /**
       * Programm entry point
       */
      int main( void ) {
      
        /* Initialize software DAC */
        soft_dac_initialize();
        
        /* Set sampling frequency */
        soft_dac_sampling_frequency(SAMPLE_RATE);
        // SAMPLE_RATE est défini dans defines.h
        
        /* Start software ADC */
        soft_dac_start();
        
        /* Infinite loop */
        for(;;) {
        }
      }
      

      Publié par skywodd | 1 décembre 2013, 18 h 11 min
  9. Bonsoir,

    merci beaucoup pour ces explication, j’y vois plus clair maintenant.

    Par contre j’ai regardé le code d’un peut plus près et il y à quelque chose que je ne comprend pas et qui me génère des erreur de compilation

    undefined reference soft_dac_initialize
    undefined reference soft_dac_start
    undefined reference soft_dac_sampling_frequency

    Dans Soft_dac.h :

    void soft_dac_initialize( void );
    void soft_dac_start( void );
    void soft_dac_sampling_frequency( uint32_t frequency );

    ces fonctions ne contiennent pas de code elles sont vide.

    est ce que tu peut m’expliquer pourquoi et d’ou viennent mes erreurs de compilation
    concernant ces 3 fonctions ?

    Merci d’avance pour tout,
    Bonne soirée,

    Cordialement,

    Siera

    Publié par siera | 2 décembre 2013, 22 h 24 min
  10. Bonsoir,

    Merci beaucoup, tout fonctionne à merveille, j’obtient de jolie courbes !!!
    je n’ai pas encore tester le second code.

    Merci pour tout

    Publié par siera | 10 décembre 2013, 21 h 38 min
  11. Comment peut on changer amplitude de ma valeur de la sinusoide de la table

    Publié par GIstain | 19 avril 2016, 3 h 15 min

Rétroliens/Pings

  1. Pingback: [Hack] Un port série … carte son. | Skyduino - Le DIY à la française - 5 juillet 2014

  2. Pingback: Waveform generator | Pearltrees - 29 octobre 2016

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

Skyduino devient Carnet du Maker

Le site Carnet du Maker remplace désormais le blog Skyduino pour tout ce qui touche à l'Arduino, l'informatique et au DIY.