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
Cara, sensassional e bem detalhado os dois Post, parabens, eu vi isso a um bom tempo atrás, foi recordar. Seria bom você fazer um vídeo ou uma série de vídeos com essa apresentação.
Muito bom ler seu comentário, Marcelo. Fico feliz que os assuntos tenham contribuído com seu conhecimento. Com certeza o vídeo ajudaria sim, mas não sei se vale a pena fazer, pois dá mais trabalho que o post escrito e o Youtube não anda valorizando muito esse tipo de conteúdo. Mas anotei aqui a ideia pra avaliar fazer isso futuramente.