O MCP4725 é um conversor DA útil na criação de sinais analógicos. Portanto, com a ajuda de um Arduino, vamos gerar um sinal alternado com ele e verificar seu desempenho. Os sinais gerados vão ser: onda quadrada, triangular, dente de serra e senoidal.

Informações básicas

Utilizando o MCP4725 com o Arduino

Em outro post, já expliquei sobre MCP4725 e ensinei a utilizar com o Arduino. Além de ter discutido sobre a limitação de velocidade da conversão DA. Portanto, recomendo dar uma lida primeiro para entender melhor a geração dos sinais.

Inclusive, neste outro post, mostrei uma função que criei chamada fast_mode. Ela serve para converter o sinal da forma mais rápida possível. A princípio, ela é uma função que recebe dois parâmetros e, mesmo isto não sendo aplicável em certos casos, utilizei esta função por ser mais rápida que a outra (normal_mode).

Obs: Daria pra mudar a função fast_mode e tirar o outro parâmetro, mas preferi não fazer isto.

Geração dos sinais e circuito

Fazendo jus ao post do site sobre geração de sinal alternado com PWM, vou replicar as mesmas ondas utilizando o MCP4725: onda quadrada, triangular, dente de serra e senoidal.

Para isto, vou utilizar um filtro passa-altas na saída do conversor DA para remover a componente contínua do sinal e deixar só a parcela alternada. A utilização ou não do filtro vai depender da sua necessidade. E, lembrando, este filtro introduzirá uma pequena atenuação no valor do sinal de saída. Veja o circuito utilizado abaixo:

Circuito Arduino e MCP4725 com filtro passa-altas

O filtro utilizado foi decidido empiricamente como de 100n. É interessante colocar uma resistência em série com ele para deixar a frequência de corte mais baixa, mas desta forma já funcionou bem.

Não foi possível calcular exatamente a frequência de corte do filtro, pois não pude saber a impedância de saída do conversor. Mesmo que no datasheet fale que ela é de 1Ω (DC), este valor não foi valido para o cálculo com o sinal alternado.

Assim como no outro post, coloquei dois parâmetros no início do código para alterar a frequência e a amplitude pico-a-pico:

1
2
3
// Tensão máxima (0-5v) e Frequência de saída
#define VMAX  3.3
#define F     1500

Gerando onda quadrada

Para a geração da onda quadrada, passei o mesmo parâmetro duas vezes para a função fast_mode.

Código

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <Wire.h>

// Endereço I2C
#define ENDERECO  0x62

// Tensão máxima (0-5v) e Frequência de saída
#define VMAX  3.3
#define F     1500

// Valores que são calculados sozinhos (não modificar)
#define DELAY       round(500000.0/(float)F - 2*85.94)
#define AMPLITUDE   round(VMAX*(4095.0/5.0))

void setup(void)
{
  // Inicializa a comunicação I2C
  Wire.begin(ENDERECO);
  Wire.setClock(400000L);
}

void loop(void)
{
  fast_mode(0, 0);
 
  if(DELAY < 16383)
  {
    delayMicroseconds(DELAY);
  }
  else
  {
    delay(DELAY/1000);
  }
 
  fast_mode(AMPLITUDE, AMPLITUDE);
 
  if(DELAY < 16383)
  {
    delayMicroseconds(DELAY);
  }
  else
  {
    delay(DELAY/1000);
  }
}


/*
 * Faz uma dupla conversão DA rápida
 * Parâmetro:
 *  output_1 - primeiro valor desejado na saída (0 - 4095)
 *  output_2 - segundo valor desejado na saída (0 - 4095)
 */

void fast_mode(uint16_t output_1, uint16_t output_2)
{
  Wire.beginTransmission(ENDERECO);

  Wire.write(output_1 >> 8);
  Wire.write(output_1 & 0xFF);
 
  Wire.write(output_2 >> 8);
  Wire.write(output_2 & 0xFF);
  Wire.endTransmission();
}

Sinal de saída

A figura abaixo mostra o sinal de saída que captei com o osciloscópio para uma frequência de 1.5kHz e Vmax de 3.3V.

Sinal de onda quadrada 1.5kHz

A frequência ficou muito próxima da desejada (0,33% de diferença), mas a tensão pico-a-pico apresentou uma diferença de ~6%. Teoricamente esta diferença de amplitude deveria ser menor, já que o MCP4725 tem uma “precisão” na faixa de alguns milivolts. Entretanto, conforme mencionado, o filtro que estamos utilizando atenua o sinal de saída, provocando uma diferença entre as amplitudes.

De todo modo, a diferença ainda é pequena para certas aplicações.

Enfim, outro problema desta geração é que o formato do sinal acaba piorando quando diminuímos a frequência dele. Veja abaixo um sinal com frequência de 20Hz.

Onda quadrada 20Hz

À medida que a frequência diminui, a onda quadrada começa a deformar apresentando uma inclinação. Isto é causado pelo alto tempo de delay entre uma conversão e outra que faz a saída do MCP4725 começar a descarregar neste meio tempo. Para resolver isto, teria que ficar atualizando a saída durante o delay. Também, se você pegar o sinal de saída antes do capacitor esse problema desaparece.

Para finalizar, resolvi tirar o delay e verificar qual é a máxima frequência gerada pelo conversor e descobri que ela é de 5852Hz.

Onda quadrada frequência máxima

A partir das imagens é possível perceber que, à medida que a frequência aumenta, o sinal é deslocado negativamente em amplitude. Para verificar isto é só olhar a variação do Vmax com a amplitude.

Além disto, é perceptível também que, na frequência mais baixa, a tensão pico-a-pico foi maior, novamente apresentando 6% de diferença do valor ideal.

Gerando onda triangular

Código

Para a onda triangular e as demais, adicionei um parâmetro para indicar por quantos pontos a onda é formada:

#define PONTOS 512

Quanto maior a quantidade de pontos, menor será frequência máxima. E não necessariamente a quantidade de pontos finais será esta, pois o arredondamento do calculo pode acabar fazendo a onda ter mais ou menos pontos do que o desejado (diferença de poucas dezenas).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <Wire.h>

// Endereço I2C
#define ENDERECO  0x62

// Tensão máxima (0-5v) e Frequência de saída
#define VMAX    3.3
#define F       15
#define PONTOS  512

// Valores que são calculados sozinhos (não modificar)    
#define AMPLITUDE   round(VMAX*(4095.0/5.0))
#define PASSO       round(2*AMPLITUDE/PONTOS)
#define PONTOS_REAL round(2*AMPLITUDE/PASSO)
#define DELAY       max(0, 2*round(1000000.0/((float)F*PONTOS_REAL) - 85.94))

void setup(void)
{
  // Inicializa a comunicação I2C
  Wire.begin(ENDERECO);
  Wire.setClock(400000L);
}

void loop(void)
{
  int16_t i;
 
  // Variação de 0 à pi da onda
  for(i = 0; i <= AMPLITUDE; i+= PASSO*2)
  {
   
    fast_mode(i, i+PASSO);
   
    if(DELAY < 16383)
    {
      delayMicroseconds(DELAY);
    }else
    {
      delay(DELAY/1000);
    }
  }

  // Variação de pi à 2pi da onda
  for(i = AMPLITUDE; i > PASSO; i -= PASSO*2)
  {
    fast_mode(i, i-PASSO);

    if(DELAY < 16383)
    {
      delayMicroseconds(DELAY);
    }
    else
    {
      delay(DELAY/1000);
    }
  }
}


/*
 * Faz uma dupla conversão DA rápida
 * Parâmetro:
 *  output_1 - primeiro valor desejado na saída (0 - 4095)
 *  output_2 - segundo valor desejado na saída (0 - 4095)
 */

void fast_mode(uint16_t output_1, uint16_t output_2)
{
  Wire.beginTransmission(ENDERECO);

  Wire.write(output_1 >> 8);
  Wire.write(output_1 & 0xFF);
 
  Wire.write(output_2 >> 8);
  Wire.write(output_2 & 0xFF);
  Wire.endTransmission();
}

Sinal de saída

A figura abaixo mostra o sinal de saída para uma frequência de 20Hz e Vmax de 3.3V.

Sinal de onda triangular frequência de 20Hz

A frequência ficou muito próxima da desejada (0,4% de diferença) assim como a tensão pico-a-pico (~3% de diferença).  A diferença da amplitude neste caso e nos seguintes (dente de serra e senoide) possivelmente é devida à forma como o programa cria a onda a partir do número de pontos, além da interferência do filtro.

Ao contrário da onda quadrada, a onda triangular é formada por vários pontos, e cada ponto gasta um certo tempo para ser gerado. Portanto, a frequência máxima será muito abaixo daquela encontrada na onda quadrada. Na prática, encontrei uma frequência máxima próxima de ~21,4Hz para 512 pontos.

Reduzindo a quantidade de pontos para 60 e colocando um DELAY de 0 para obter uma frequência maior, consegui chegar em 188Hz. Veja o resultado abaixo.

Onda triangular frequência máxima

Repare que, por ser uma frequência maior e ter uma quantidade de pontos diferente de antes, houve uma pequena diferença da amplitude real para a desejada (~0,6%).

É possível chegar a uma frequência maior reduzindo a quantidade de pontos, mas abaixo de 60 pontos a onda começou a ficar com muitos degraus. Então, resolvi usar 60 pontos como o limiar.

Gerando onda dente de serra

Código

Assim como na onda triangular, também é preciso definir o número de pontos.

#define PONTOS 512

E, quanto maior a quantidade de pontos, menor será frequência máxima.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <Wire.h>

// Endereço I2C
#define ENDERECO  0x62

// Tensão máxima (0-5v) e Frequência de saída
#define VMAX    4.3
#define F       22
#define PONTOS  512

// Valores que são calculados sozinhos (não modificar)    
#define AMPLITUDE   round(VMAX*(4095.0/5.0))
#define PASSO       round(2*AMPLITUDE/PONTOS)
#define PONTOS_REAL round(2*AMPLITUDE/PASSO)
#define DELAY       max(0, 2*round(1000000.0/((float)F*PONTOS_REAL) - 85.94))

void setup(void)
{
  // Inicializa a comunicação I2C
  Wire.begin(ENDERECO);
  Wire.setClock(400000L);
}

void loop(void)
{
  int16_t i;
 
  for(i = 0; i <= AMPLITUDE; i+= PASSO)
  {
   
    fast_mode(i, i+(PASSO/2));
   
    if(DELAY < 16383)
    {
      delayMicroseconds(DELAY);
    }else
    {
      delay(DELAY/1000);
    }
  }
}


/*
 * Faz uma dupla conversão DA rápida
 * Parâmetro:
 *  output_1 - primeiro valor desejado na saída (0 - 4095)
 *  output_2 - segundo valor desejado na saída (0 - 4095)
 */

void fast_mode(uint16_t output_1, uint16_t output_2)
{
  Wire.beginTransmission(ENDERECO);

  Wire.write(output_1 >> 8);
  Wire.write(output_1 & 0xFF);
 
  Wire.write(output_2 >> 8);
  Wire.write(output_2 & 0xFF);
  Wire.endTransmission();
}

Sinal de saída

A figura abaixo mostra o sinal de saída para uma frequência de 15Hz e Vmax de 4.3V.

Sinal de onda dente de serra frequência de 15Hz

A frequência ficou próxima da desejada (2,4% de diferença), mas tensão pico-a-pico ficou 5% distante devido aos mesmos problemas de antes.

Além disto, a onda ficou um pouco arredondada, sendo um problema causado pela função fast_mode, pois ela gera dois pontos consecutivos sem delay e isto acaba causando um efeito de arredondando na onda completa.

Em relação à frequência máxima, encontrei um valor próximo de ~22,3Hz para 512 pontos. É um valor muito próximo ao da onda triangular, isto porque o modelo do algoritmo é praticamente o mesmo.

Reduzindo a quantidade de pontos para 40 e colocando um DELAY de 0 para obter uma frequência maior, consegui chegar em 290,8Hz. Veja o resultado abaixo.

Obs: mudei a amplitude para 3,3V.

Assim como na onda triangular, quando a quantidade de pontos foi alterada e a frequência aumentou, a diferença da amplitude real e a desejada diminuiu bastante (~0,6%)

É possível chegar a uma frequência maior reduzindo a quantidade de pontos, mas abaixo de 40 pontos a onda começou a ficar com muitos degraus. Então, resolvi usar 40 pontos como o limiar.

Gerando onda senoidal

Código

No exemplo da senoide da biblioteca da Adafruit, existe um vetor já gerado com a senoide. Entretanto, por conta da personalização da quantidade de pontos e da amplitude, resolvi gerar a senoide dentro do próprio código.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <Wire.h>
#include <math.h>

// Endereço I2C
#define ENDERECO  0x62

// Tensão máxima (0-5v) e Frequência de saída
#define VMAX    3.3
#define F       23
#define PONTOS  512

// Valores que são calculados sozinhos (não modificar)    
#define AMPLITUDE   round(VMAX*(4095.0/5.0))
#define PASSO       round(2*AMPLITUDE/PONTOS)
#define PONTOS_REAL round(2*AMPLITUDE/PASSO)
#define DELAY       max(0, 2*round(1000000.0/((float)F*PONTOS_REAL) - 85.94))

uint16_t senoide[PONTOS_REAL] = {0};

void setup(void)
{
  // Inicializa a comunicação I2C
  Wire.begin(ENDERECO);
  Wire.setClock(400000L);

  // Constroi a senoide
  for(uint16_t i = 0; i < PONTOS_REAL; i++)
  {
    senoide[i] = (AMPLITUDE/2.0)*sin(2*PI*i/(float)PONTOS_REAL) + AMPLITUDE/2.0;
  }
}

void loop(void)
{
  uint16_t i;
 
  for(i = 0; i <= PONTOS_REAL-1; i+=2)
  {
    fast_mode(senoide[i], senoide[i+1]);
   
    if(DELAY < 16383)
    {
      delayMicroseconds(DELAY);
    }else
    {
      delay(DELAY/1000);
    }
  }
}


/*
 * Faz uma dupla conversão DA rápida
 * Parâmetro:
 *  output_1 - primeiro valor desejado na saída (0 - 4095)
 *  output_2 - segundo valor desejado na saída (0 - 4095)
 */

void fast_mode(uint16_t output_1, uint16_t output_2)
{
  Wire.beginTransmission(ENDERECO);

  Wire.write(output_1 >> 8);
  Wire.write(output_1 & 0xFF);
 
  Wire.write(output_2 >> 8);
  Wire.write(output_2 & 0xFF);
  Wire.endTransmission();
}

Sinal de saída

A figura abaixo mostra o sinal de saída para uma frequência de 10Hz e Vmax de 1.8V.

Sinal de onda senoidal frequência de 10Hz

A frequência ficou próxima da desejada (~2,7% de diferença), assim como a tensão pico-a-pico (~1,1% de diferença). 

Em relação à frequência máxima, encontrei um valor próximo de ~21,56Hz para 512 pontos. É um valor semelhante ao da onda triangular e da dente de serra, pois o modelo do algoritmo é praticamente o mesmo.

Reduzindo a quantidade de pontos para 90 e colocando um DELAY de 0 para obter uma frequência maior, consegui chegar em 129,8Hz. Veja o resultado abaixo.

Obs: mudei a amplitude para 3,3V.

Onda senoidal frequência máxima

É possível chegar a uma frequência maior reduzindo a quantidade de pontos, mas em 90 pontos a senoide já estava levemente deformada e abaixo disto ficava pior. Então, resolvi usar 90 pontos como o limiar.

Observações finais

A geração de sinal utilizando o MCP4725 se mostrou muito satisfatória em termos da variação da amplitude e da frequência quando comparamos com a geração via PWM com filtro passa-baixas. Isto porque, foi possível conseguir frequências maiores nas 4 ondas e uma amplitude bem mais próxima da desejada.

Em relação às frequências máxima:

  • Onda quadrada
    • PWM: 80Hz
    • MCP4725: 5kHz
  • Onda triangular
    • PWM: 120Hz
    • MCP4725: 188Hz
  • Onda dente de serra
    • PWM: não escrevi no post, mas deve ser próximo da triangular (120Hz).
    • MCP4725: 290Hz
  • Onda senoidal
    • PWM: 80Hz
    • MCP4725: 129Hz

E em relação à amplitude, no caso do PWM usamos um filtro que acaba atenuando bastante o sinal de saída quanto maior for a frequência desejada. Já com o MCP4725, isso não aconteceu, pois utilizamos apenas um filtro passa-altas para manter o sinal alternado. E o controle de tensão do conversor DA é mais exato que o do PWM.

Gerando sinal alternado com Arduino