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:

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.

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.

À 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.

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.

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.

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.

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.

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.

É 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