O MCP4725 é capaz de converter um sinal digital em um analógico com boa resolução e rapidez. Portanto, vamos aprender as características do MCP4725 e como utilizá-lo com o Arduino (com e sem bibliotecas).

Informações básicas

O que é o MCP4725

O MCP4725 é um pequeno CI conversor DA (digital-analógico). Isto é, a partir de um sinal digital (via interface I2C), ele gera um sinal analógico correspondente (0-5V por padrão). Ele pode ser encontrado no módulo da imagem abaixo.

Módulo MCP4725

Aplicações

Ele pode ser usado para gerar:

  • Sinais conhecidos
    • Senoide.
    • Onda quadrada.
    • Onda triangular.
    • Etc…
  • Outros tipos de sinais
    • Nota de um instrumento.
    • Voz.
    • Som aleatório.
  • Níveis de tensão
    • Pode ser usado para controlar algum circuito que depende do valor de tensão.
    • Ex: uma saída 4-20mA controlada por tensão.

Limitações da conversão

Apesar de ter mencionado uma gama boa de aplicações no tópico acima, existem limitações que nos impedirão de reproduzir os sons de forma fidedigna com o Arduino.

A primeira se trata da velocidade de conversão. Mesmo que o MCP4725 seja capaz de fazer uma conversão rápida, como comentei na introdução do post, sua utilização com o Arduino não é capaz de gerar os sinais tão rapidamente. Portanto, a frequência do sinal de saída será limitada a algumas dezenas de Hertz. Isto será visto com mais detalhes no tópico da utilização com o Arduino.

A segunda se trata da memória interna do Arduino. Isto porque, um bom sinal de áudio, mesmo de curta duração, vai possuir uma quantidade grande de dados, chegando a algumas centenas de kBytes em alguns casos. E isto é um problema para o Arduino, já que sua memória interna é de 32kBytes e uma parcela disto é ocupada pelo código interno do Arduino. Portanto, fica complicado armazenar áudios mais complexos em sua memória interna.

Ligando o módulo em um alto-falante

Seguindo a ideia de gerar um sinal de áudio com o MCP4725, você pode querer ligar ele em um alto-falante diretamente. Mas isto não é possível, pois o MCP4725 é capaz de “alimentar” uma carga de no mínimo 5kΩ e os alto-falantes normalmente possuem uma baixa impedância (4 – 16Ω).

De acordo com o datasheet (pág 7), abaixo de 1kΩ, a tensão de saída começa a cair exponencialmente à medida que a resistência da carga diminui. De 1kΩ à 5kΩ a tensão de saída não chega a sofrer atenuação significativa, mesmo o limite sendo 5kΩ.

Características do MCP4725

Neste tópico, irei mostrar as características do CI MCP4725 e do módulo apresentado anteriormente. E não serão informações cruciais para a implementação no Arduino. Então, se desejar, pode pular para o tópico seguinte.

Características

  • 2.7V a 5V de alimentação (Vdd).
  • 12 bits de resolução.
    • 4096 valores entre 0 e Vdd (incluindo ambos).
  • INL típico de +- 2LSB (bit menos significativo).
    • É a diferença entre o valor real e o ideal. 
    • Em 5V, essa diferença pode ser de +-2,4mV.
  • 6μs de tempo de acomodação do sinal de saída.
    • Este tempo aumenta um pouco (< 10μs) quando o CI sai do modo power-down e volta ao modo de operação normal.
  • Interface I2C:
    • Endereço ajustável (veremos adiante).
    • Velocidade de 100k, 400k ou 3.4MBps.
  • Modos de operação:
    • Normal.
    • Power-down (baixo consumo).
      • Neste modo, a maioria dos circuitos internos são desligados com exceção da interface I2C. Ou seja, neste modo o MCP4725 fica “inativo”.
  • Consumo de corrente:
    • Típica de 210μA e máxima de 400μA em pleno funcionamento.
    • Típica de 0.06μA e máxima de 2μA em modo power-down.
  • Impedância de saída (DC):
    • 1Ω no modo normal.
    • 1kΩ no modo Power-Down 1.
    • 100kΩ no modo Power-Down 2.
    • 500kΩ no modo Power-Down 3.
  • Possui um registrador de 19 bits (DAC register) responsável por:
    • Armazenar o valor digital de 12 bits que é colocado na saída QUANDO está em pleno funcionamento.
    • Armazenar bits de configuração (Power-Down).
    • Armazenar bits pertinentes para o funcionamento do CI.
  • Possui uma memória EEPROM de 14bits responsável por:
    • Armazenar o valor digital de 12 bits que é colocado na saída QUANDO o circuito entra em Power-Down ou quando o circuito é resetado.
    • Armazenar bits de configuração (Power-Down).

Endereço

O endereço do MCP4725 é formado pelos seguintes bits:

1100 A2 A1 A0

Sendo que:

  • A2 e A1 são bits definidos pelo próprio fabricante durante o processo de fabricação.
    • No datasheet fala que A2 e A1 são 0 por padrão, mas meu módulo veio com A2 = 0 e A1 = 0.
  • A0 é um bit definido pelo pino A0 do CI.

No meu caso, o módulo veio com o endereço 0x62 (1100 010). Entretanto, ainda é possível configurar o bit A0. E isto é feito por meio de uns pads de acordo com a imagem abaixo (indicado pelo retângulo amarelo).

Configurando o endereço do MCP4725

A partir destes pads, é possível soldar o pad central (A0) com o pad da esquerda (GND) ou o pad central com o da direita (Vcc). Por padrão, o bit A0 já é 0, então não é estritamente necessário realizar esta solda. Mas, para alterar o bit para 1, basta soldar o pad central com o da direita.

Com isto, o endereço passará a ser 0x63 (1100 011).

Pinos

A partir da última imagem acima, vemos que o MCP4725 possui 6 pinos:

  • Out – Sinal analógico de saída gerado pelo conversor.
  • 2x GND – Terra que pode ser usado para alimentar o módulo ou ser usado como referência para o sinal de saída (Out).
  • SCL – Pino de Clock da comunicação I2C.
  • SDA – Pino de dados da comunicação I2C.
  • Vcc – Pino de alimentação do módulo (2.7 a 5V).

Circuitos possíveis

No datasheet do CI (pág 29-33), é possível encontrar alguns circuitos recomendados para diversas aplicações diferentes:

  • Controlar a corrente de saída a partir de tensão. O que possibilita a criação do padrão industrial de 4-20mA.
  • Gerar uma saída com uma faixa de tensão ajustável (incluindo negativa).
  • Entre outros…

Circuito do módulo

Depois de dar uma analisada no modulo, recriei o seu esquemático que pode ser visto na imagem abaixo. Na parte de baixo da placa existem alguns pads para conectar a interface I2C nos resistores de pull-up (4.7k), mas eles já vem conectados, então resolvi não representá-los.

Esquemático do modulo MCP4725

Utilizando o MCP4725 com o Arduino (com biblioteca)

Circuito

Tendo em vista a pinagem discutida no tópico anterior, o circuito base para o restante do post é o da imagem abaixo:

Circuito Arduino e MCP4725

“Ponto de prova” são as pontas do osciloscópio que eu vou ligar na saída do MCP4725 para analisar o sinal gerado. Mas você pode ligar a saída em qualquer lugar desejado.

Baixando biblioteca

A biblioteca pode ser baixada na própria IDE do Arduino em Sketch -> Incluir Biblioteca -> Gerenciar Bibliotecas… E é só pesquisar por MCP4725 e baixar a primeira opção que é da Adafruit.

Esta biblioteca vem com dois exemplos, um para gerar uma senoide e outro uma onda triangular que são bem úteis para testar o módulo.

A biblioteca é bem simples e possui apenas duas funções (begin e setVoltage). Pra fazer o código ficar mais rápido, preferi criar alguns comandos e não depende da biblioteca. Mas, antes, pretendo explicar os comandos da biblioteca abaixo.

Problemas de endereço

Se você não conseguir fazer o módulo funcionar, talvez o endereço do seu módulo seja diferente do meu. Para descobrir exatamente qual é, recomendo rodar este código do I2cScanner.

Comandos disponíveis

Para utilizar a biblioteca da Adafruit você precisa criar um instância da classe Adafruit_MCP4725. E isto é feito da seguinte forma:

  • Adafruit_MCP4725 dac;
    • dac é o nome da instância.

Com isto, você pode utilizar os seguintes comandos:

  • dac.begin(endereço);
    • Inicializa a comunicação I2C utilizando o endereço especificado (ex: 0x62).
  • dac.setVoltage(valor_saida, condição);
    • Define a tensão de saída do módulo igual a valor_saida.
    • Se condição for true, ele escreve o valor na memória EEPROM também. Senão (false), apenas no registrador DAC.

Utilizando o MCP4725 com o Arduino (sem biblioteca)

Circuito

É o mesmo do tópico anterior.

Comandos criados

Quando resolvi evitar o uso da biblioteca, foi para obter um programa mais ágil para poder gerar sinais de maiores frequência. Após criar minhas próprias funções com base na biblioteca e no datasheet, não cheguei a comparar se de fato teve melhoras, mas acabei mantendo a ideia, pois a comunicação é fácil de ser feita.

E implementei também uma função para comunicar no modo “fast” do MCP4725. Que de fato agilizou a execução e é um comando que não existe na biblioteca da Adafruit.

Sendo assim, o primeiro passo para fugir da biblioteca foi inicializar a comunicação I2C no void setup:

Obs: Para isso, é necessário incluir a biblioteca Wire.h

1
2
Wire.begin(ENDERECO);
Wire.setClock(400000L);

Feito isto, basta utilizar alguns dos comandos abaixo. O comando normal_mode foi adaptado da biblioteca da Adafruit.

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
/*
 * Faz uma conversão DA normal
 * Parâmetro:
 *  output - valor desejado na saída (0 - 4095)
 */

void normal_mode(uint16_t output)
{
  Wire.beginTransmission(ENDERECO);
  Wire.write(0x40);
 
  Wire.write(output >> 4);         // (D11.D10.D9.D8.D7.D6.D5.D4)
  Wire.write((output & 0xF) << 4); //(D3.D2.D1.D0.0.0.0.0)
  Wire.endTransmission();
}

/*
 * 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();
}

O comando fast_mode possui dois parâmetros de valor de saída, pois é possível enviar duas conversões em sequência para economizar ainda mais tempo.

Limitação de velocidade

Conforme dito, a comunicação entre o Arduino e o MCP4725 é feita via I2C e a velocidade máxima do I2C é de 400kHz no Arduino. Pode parecer uma velocidade alta, mas, mesmo no modo fast, o Arduino precisa enviar 3 bytes (24 bits) para fazer uma conversão. E cada bit é enviado em um pulso do clock (1/400kHz = 2.5μs). Ou seja, 24 bits * 2.5μs = 60μs gastos a cada conversão o que possibilita uma frequência máxima de 16,67kHz por ponto.

Mesmo que a frequência de cada ponto seja “elevada”, ainda é um frequência baixa para se gerar um sinal com boa resolução. Por exemplo uma senoide de 512 pontos: se multiplicarmos os 60μs pelos 512 pontos, obtemos ~30ms, que corresponde a uma frequência de ~33Hz.

Portanto, a frequência teórica máxima para um sinal de 512 pontos é de ~33Hz, que é muito baixa para uma boa gama de aplicações. E este valor é ainda mais baixo se formos considerar o tempo de conversão na prática, pois existem outros procedimentos que o microcontrolador realiza que geram atraso. Para descobrir isto, criei um código em que o microcontrolador realizar 512 conversões e calcula o tempo médio gasto em cada ponto (em μs):

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
#include <Wire.h>

// Endereço I2C
#define ENDERECO  0x62

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

void loop(void) {
  uint16_t i;
  unsigned long timeout = 0;

  timeout = millis(); // Variavel auxiliar para calcular o tempo gasto no for

  for (i = 0; i < 512; i += 2)
  {
    fast_mode(i, i+1);
  }

  // Calcula quanto tempo gastou no for e divide por 512 (quantidade de pontos)
  Serial.println(1000*(millis() - timeout)/512.0);
}


/*
 * Faz uma conversão DA normal
 * Parâmetro:
 *  output - valor desejado na saída (0 - 4095)
 */

void normal_mode(uint16_t output)
{
  Wire.beginTransmission(ENDERECO);
  Wire.write(0x40);
 
  Wire.write(output >> 4);         // (D11.D10.D9.D8.D7.D6.D5.D4)
  Wire.write((output & 0xF) << 4); //(D3.D2.D1.D0.0.0.0.0)
  Wire.endTransmission();
}

/*
 * 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();
}

Depois de executar o código, o monitor serial imprimiu o valor de 85.94 diversas vezes. Ou seja, o tempo real gasto para fazer uma conversão é de ~86μs. Com isto, a máxima frequência real para um sinal de 512 pontos é de ~22Hz. E isto será visto com detalhes em outro post.

Cheguei a trocar o comando fast_mode pelo normal_mode e obtive um valor de 140.63, o que implica em uma frequência máxima de ~14Hz para 512 pontos.

Gerando sinais

Com as funções apresentadas você já é capaz de gerar qualquer tensão desejada na saída do MCP4725.

Os exemplos da geração de sinais vou deixar para outro post onde irei detalhar mais este assunto:

Gerando sinal alternado com MCP4725