Iniciando a contrução de um gerador de sinais usando o DDS AD9850.

Há algumas semanas comecei a idealizar um gerador de sinais usando um modulo DDS AD9850 comprado a anos atrás, o qual não sabia se estava funcionado.
Com base em uma placa Arduino Uno R3, liguei o modulo DDS, fiz um Sketch básico seguindo o exemplo, e conectei uma das saídas no Osciloscópio. Feito o upload, uma forma de onda apareceu na tela do Osciloscópio, e mostrando a frequência programada.
Em seguida conectei um LCD 16x2 via o modulo LCD-I2C, e testei com o Sketch exemplo.
Inclui o LCD no código do gerador DDS.
Precisava de uma forma mais prática de entrar com a frequência do que via monitor serial, adicionei um teclado 3x4 de telefone. Código testado a parte e integrado ao do DDS.
Assim, construí um gerador de sinais rudimentar.
Para dar continuidade ao projeto, resolvi que usar a placa Arduino não seria muito prático, então comprei alguns Atmega328p, e programei o bootloader. Montando o mínimo necessário para o seu funcionamento.

Atualmente tenho tudo montando em um breadboard/protoboard, a intenção e passar para uma PCI.

Também foi adicionado rotinas para usar a eeprom para armazenar a frequência de calibração e a última frequência usada. Essa parte do eeprom me deixou confuso, já que, deveria armazenar 124998500, mas estava obtendo 124998496. Esse número estava declarado como double. Ficou correto após declarar como long.

E o código do Sketch atual e esse:

/*
* Sketch de um protopito de gerador de sinais usando o modulo AD9850
* 
* Autor: Paulino Kenji Sato <pksato+arduino@gmail.com>
* Data: 22/07/2020
* 
* Modulos usados:
* Chip Atmega328P com bootloader (Arduino Uno R3), comprado pelo mercado livre
* Modulo DDS AD9850 (comprado em site chines)
* Modulo LCD 16x2, reaproveitado.
* Modulo I2C para LCD, comprado no site Baú da Eletronica.
* Teclado 3x4, reaproveitado de um telefone sem fio antigo.
* 
* Objetivos:
* Programar o DDS AD9850 para gerar uma frequencia qualquer.
* Ler a frequencia desejada digitado no teclado.
* Apresentar a frequencia no display LCD.
* Gravar e Ler os parametros na eeprom.
* Ler o pino A0/23 e conforme o estado, usar ou não a eeprom.
* 
*/

#include <Wire.h>
#include <EEPROM.h>
#include <AD9850.h> // Instalado manualmente http://github.com/F4GOJ/AD9850
#include <LiquidCrystal_I2C.h> // Biblioteca do Arduino https://github.com/johnrickman/LiquidCrystal_I2C
#include <Keypad.h>  // Biblioteca do Arduino https://playground.arduino.cc/Code/Keypad/

// IO Geral
const int GPIN_EEP_RW = A0; // Usa ou não os dados a eeprom, leitura e escrita, HIGH=usa, LOW=não usa.

// Eeprom OBS.: Uma struc simplicaria o endereçamento
const int EEP_SIGADDR = 0; // Endereço da assinatura para verificar se a eeprom contem a assinatura ZDDS
const int EEP_DDS_CALXTAL = 4; // Endereço do parametro DDS_CALXTAL
const int EEP_DDS_STARTFRQ = 8; // Endereço do parametro DDS_STARTFRQ
const long EEP_SIGNATURE = 0x5A444453; // Assinatura para validar a eeprom (ZDDS)

// Definições do DDS AD9850
const int DDS_WCLK = 10; // Pino de Clock
const int DDS_FQUD = 11; // Pino de Frequency update
const int DDS_DATA = 12; // Pino de Data
const int DDS_REST = 13; // Pino de Reset
const long DDS_NOCALXTAL = 125000000; // Frequencia nominal do cristal.
const long DDS_MAXFRQ = 40000000; // Frequencia máxima do AD9850
const int DDS_PHASE = 0; // Fase do DDS. Sempre Zero
long DDS_CALXTAL = 124998500;  // Frequencia calibrada do cristal do DDS, armazenar na eeprom
long DDS_STARTFRQ = 10000000;  // Frequencia inicial, armazenar na eeprom

// Definições do Teclado
const byte TLinhas = 4; // Quatro linhas
const byte TColunas = 3; // Trez Colunas
char teclas[TLinhas][TColunas] = {
 {'1','2','3'},
 {'4','5','6'},
 {'7','8','9'},
 {'*','0','#'}
};
byte PinosLinhas[TLinhas] = {6, 5, 4, 3};
byte PinosColunas[TColunas] = {9, 8, 7};
Keypad teclado = Keypad( makeKeymap(teclas), PinosLinhas, PinosColunas, TLinhas, TColunas );

// Definições do LCD 16 Colunas e 2 Linhas
LiquidCrystal_I2C lcd(0x27,16,2);

// String da frequencia atual e da anterior.
String sFrequencia = "";
String aFrequencia = String(DDS_STARTFRQ);

// Função para escrever na segunda linha do LCD a frequencia, alinhada a direita e terminado em Hz.
void lcdfrq(String frequencia) {
    lcd.setCursor(0,1);
    lcd.print("              Hz"); // Apaga (escreve espaços) a linha e termina em Hz
    lcd.setCursor(14-frequencia.length(),1);
    lcd.print(frequencia);
}

void setup() {
 pinMode(GPIN_EEP_RW, INPUT_PULLUP);
 long eep_tmp = 0; // variavel temporaria para ler a eeprom;
 lcd.init();
 lcd.setCursor(0,0);
 lcd.print("DDS AD9850 Teste");
 // Se GPIN_EEP_RW estiver aberto ou em +5V, usa os dados da eeprom.
 if (digitalRead(GPIN_EEP_RW) == HIGH ) {
    // Possui uma assinature válida?
    EEPROM.get(EEP_SIGADDR, eep_tmp);
    if (eep_tmp == EEP_SIGNATURE ) { // E valida
       // le a frequencia de calibração
       eep_tmp=0;
       EEPROM.get(EEP_DDS_CALXTAL, eep_tmp);
       // Verifica se a frequencia não esta muito diferenete da DDS_NOCALXTAL
       int eep_diff = DDS_NOCALXTAL - eep_tmp;
       if (abs(eep_diff) <= 5000 ) {
          DDS_CALXTAL=eep_tmp;
       }
       // le a frequencia inicial
       eep_tmp=0;    
       EEPROM.get(EEP_DDS_STARTFRQ, eep_tmp);
       // Verifica se a frequencia esta dentro da capacidade do AD9850 de 1 a DDS_MAXFRQ
       if (eep_tmp > 0 && eep_tmp <= DDS_MAXFRQ) {
          DDS_STARTFRQ=eep_tmp;
          aFrequencia = String(DDS_STARTFRQ);
       }     
    } else { 
       // grava os parametros iniciais na eeprom.
       EEPROM.put(EEP_SIGADDR, EEP_SIGNATURE);
       EEPROM.put(EEP_DDS_CALXTAL, DDS_CALXTAL);
       EEPROM.put(EEP_DDS_STARTFRQ, DDS_STARTFRQ);
    }  // fim EEP_SIGNATURE
 } // fim GPIN_EEP_RW
 DDS.begin(DDS_WCLK, DDS_FQUD, DDS_DATA, DDS_REST);
 DDS.calibrate((double)DDS_CALXTAL);  // A lib DDS pede um double, então e feito um cast por garantia.
 DDS.setfreq((double)DDS_STARTFRQ, DDS_PHASE);
 lcd.setCursor(0,1);
 lcdfrq(aFrequencia);
}

void loop() {
  char Tecla = teclado.getKey();
  if (Tecla) {
     if (isDigit(Tecla)) {
        sFrequencia += Tecla;
        lcdfrq('#'+sFrequencia);
     } else {       
        if (Tecla == '#' && sFrequencia.toDouble() > 0) {
           DDS.setfreq(sFrequencia.toDouble(), DDS_PHASE);
           if (digitalRead(GPIN_EEP_RW) == HIGH ) {  // Se GPIN_EEP_RW estiver aberto/alto grava na EEPROM
              EEPROM.put(EEP_DDS_STARTFRQ, sFrequencia.toInt());
           }
           lcdfrq(sFrequencia);
           aFrequencia=sFrequencia;
           sFrequencia = "";
        } else {
           lcdfrq(aFrequencia);
           sFrequencia = "";
        }
     }
  }
}
[code]

Boa dia.
Adicionei um encoder rotativo, reaproveitado de um mouse.
Ainda tem os três botões do mouse que não dei uma destinação.
E montei o conjunto em uma base feito de papelão.

/*
 * Sketch de um protopito de gerador de sinais usando o modulo AD9850
 * 
 * Autor: Paulino Kenji Sato <pksato+arduino@gmail.com>
 * Data: 22/07/2020
 * Encoder adicionado: 30/07/2020
 * 
 * Modulos usados:
 * Chip Atmega328P com bootloader (Arduino Uno R3), comprado pelo mercado livre
 * Modulo DDS AD9850 (comprado em site chines)
 * Modulo LCD 16x2, reaproveitado.
 * Modulo I2C para LCD, comprado no site Baú da Eletronica.
 * Teclado 3x4, reaproveitado de um telefone sem fio antigo.
 * 
 * Objetivos:
 * Programar o DDS AD9850 para gerar uma frequencia qualquer.
 * Ler a frequencia desejada digitado no teclado.
 * Apresentar a frequencia no display LCD.
 * Gravar e Ler os parametros na eeprom.
 * Ler o pino A3/26 e conforme o estado, usar ou não a eeprom.
 * 
 */

#include <Wire.h>
#include <EEPROM.h>
#include <AD9850.h> // Instalado manualmente http://github.com/F4GOJ/AD9850
#include <LiquidCrystal_I2C.h> // Biblioteca do Arduino https://github.com/johnrickman/LiquidCrystal_I2C
#include <Keypad.h>  // Biblioteca do Arduino https://playground.arduino.cc/Code/Keypad/


// IO Geral
#define GPIN_EEP_RW A3  // Usa ou não os dados a eeprom, leitura e escrita, HIGH=usa, LOW=não usa.
#define GPIN_ROTENC_A A0 // Encoder pino A pullup via resistores de 10k
#define GPIN_ROTENC_B A1 // Encoder pino B Deboucing com dois copacitores de 10nF 

// Eeprom OBS.: Uma struc simplicaria o endereçamento
const int EEP_SIGADDR = 0; // Endereço da assinatura para verificar se a eeprom contem a assinatura ZDDS
const int EEP_DDS_CALXTAL = 4; // Endereço do parametro DDS_CALXTAL
const int EEP_DDS_STARTFRQ = 8; // Endereço do parametro DDS_STARTFRQ
const long EEP_SIGNATURE = 0x5A444453; // Assinatura para validar a eeprom (ZDDS)

// Definições do DDS AD9850
#define DDS_WCLK 10 // Pino de Clock
#define DDS_FQUD 11 // Pino de Frequency update
#define DDS_DATA 12 // Pino de Data
#define DDS_REST 13 // Pino de Reset
const long DDS_NOCALXTAL = 125000000; // Frequencia nominal do cristal.
const long DDS_MAXFRQ = 40000000; // Frequencia máxima do AD9850
const int DDS_PHASE = 0; // Fase do DDS. Sempre Zero
long DDS_CALXTAL = 124998500;  // Frequencia calibrada do cristal do DDS, armazenar na eeprom
long DDS_STARTFRQ = 10000000;  // Frequencia inicial, armazenar na eeprom
long DDS_FREQUENCIA = 10000000; // Frequencia Atual

// Definições do Teclado
const byte TLinhas = 4; // Quatro linhas
const byte TColunas = 3; // Trez Colunas
char teclas[TLinhas][TColunas] = {
  {'1','2','3'},
  {'4','5','6'},
  {'7','8','9'},
  {'*','0','#'}
};
byte PinosLinhas[TLinhas] = {6, 5, 4, 3};
byte PinosColunas[TColunas] = {9, 8, 7};
Keypad teclado = Keypad( makeKeymap(teclas), PinosLinhas, PinosColunas, TLinhas, TColunas );

// Definições do LCD 16 Colunas e 2 Linhas
LiquidCrystal_I2C lcd(0x27,16,2);

// String da frequencia atual e da anterior.
String sFrequencia = "";
String aFrequencia = String(DDS_STARTFRQ);

// Estado do Encoder
int ENC_ANT; // Estado anterior
int ENC_COR; // Estado atual

// Função para escrever na segunda linha do LCD a frequencia, alinhada a direita e terminado em Hz.
void lcdfrq(String frequencia) {
     lcd.setCursor(0,1);
     lcd.print("              Hz"); // Apaga (escreve espaços) a linha e termina em Hz
     lcd.setCursor(14-frequencia.length(),1);
     lcd.print(frequencia);
}

void setup() {  
  pinMode(GPIN_EEP_RW, INPUT_PULLUP);
  pinMode(GPIN_ROTENC_A, INPUT);
  pinMode(GPIN_ROTENC_B, INPUT);
  long eep_tmp = 0; // variavel temporaria para ler a eeprom;
  lcd.init();
  lcd.setCursor(0,0);
  lcd.print("DDS AD9850 Teste");
  // Se GPIN_EEP_RW estiver aberto ou em +5V, usa os dados da eeprom.
  if (digitalRead(GPIN_EEP_RW) == HIGH ) {
     // Possui uma assinature válida?
     EEPROM.get(EEP_SIGADDR, eep_tmp);
     if (eep_tmp == EEP_SIGNATURE ) { // E valida
        // le a frequencia de calibração
        eep_tmp=0;
        EEPROM.get(EEP_DDS_CALXTAL, eep_tmp);
        // Verifica se a frequencia não esta muito diferenete da DDS_NOCALXTAL
        int eep_diff = DDS_NOCALXTAL - eep_tmp;
        if (abs(eep_diff) <= 5000 ) {
           DDS_CALXTAL=eep_tmp;
        }
        // le a frequencia inicial
        eep_tmp=0;    
        EEPROM.get(EEP_DDS_STARTFRQ, eep_tmp);
        // Verifica se a frequencia esta dentro da capacidade do AD9850 de 1 a DDS_MAXFRQ
        if (eep_tmp > 0 && eep_tmp <= DDS_MAXFRQ) {
           DDS_STARTFRQ=eep_tmp;
           DDS_FREQUENCIA=eep_tmp;
           aFrequencia = String(DDS_STARTFRQ);
        }     
     } else { 
        // grava os parametros iniciais na eeprom.
        EEPROM.put(EEP_SIGADDR, EEP_SIGNATURE);
        EEPROM.put(EEP_DDS_CALXTAL, DDS_CALXTAL);
        EEPROM.put(EEP_DDS_STARTFRQ, DDS_STARTFRQ);
     }  // fim EEP_SIGNATURE
  } // fim GPIN_EEP_RW
  DDS.begin(DDS_WCLK, DDS_FQUD, DDS_DATA, DDS_REST);
  DDS.calibrate((double)DDS_CALXTAL);  // A lib DDS pede um double, então e feito um cast por garantia.
  DDS.setfreq((double)DDS_STARTFRQ, DDS_PHASE);
  lcd.setCursor(0,1);
  lcdfrq(aFrequencia);
  ENC_ANT=digitalRead(GPIN_ROTENC_A);
  ENC_COR=ENC_ANT;
}

void loop() {
   static int frq_entra = 0; // Se esta sendo feito uma entrada no teclado.
   char Tecla = teclado.getKey();
   if (Tecla) {
      if (isDigit(Tecla)) {
         sFrequencia += Tecla;
         lcdfrq('#'+sFrequencia);
         frq_entra=1; // Lendo a frequencia
      } else {
         if (Tecla == '#' && sFrequencia.toDouble() > 0) {
            DDS_FREQUENCIA=sFrequencia.toInt();
            DDS.setfreq((double)DDS_FREQUENCIA, DDS_PHASE);
            if (digitalRead(GPIN_EEP_RW) == HIGH ) {  // Se GPIN_EEP_RW estiver aberto/alto grava na EEPROM
               EEPROM.put(EEP_DDS_STARTFRQ, DDS_FREQUENCIA);
            }
            lcdfrq(sFrequencia);
            aFrequencia=sFrequencia;
            sFrequencia = "";
         } else {
            lcdfrq(aFrequencia);
            sFrequencia = "";
         }
         frq_entra=0; // Entrada finalizada ou cancelada
      }
   }
   if (frq_entra==0) { // Faz a leitura do encoder
      // https://howtomechatronics.com/tutorials/arduino/rotary-encoder-works-use-arduino/
      ENC_COR=digitalRead(GPIN_ROTENC_A);
      if (ENC_COR!=ENC_ANT) {
        if (digitalRead(GPIN_ROTENC_B)!=ENC_COR) {
          // soma
          if (DDS_FREQUENCIA <= DDS_MAXFRQ) {
             DDS_FREQUENCIA++;
          }
        } else {
          // subtrai
          if (DDS_FREQUENCIA > 0) {
             DDS_FREQUENCIA--;
          }
        }
        DDS.setfreq((double)DDS_FREQUENCIA, DDS_PHASE);
        aFrequencia = String(DDS_FREQUENCIA);
        lcdfrq(aFrequencia);
      }
      ENC_ANT=ENC_COR;
   }
}