Leer señal en pin analogico, y recuperarlo con MCP4922 con SPI

hola chicos, buenas tardes tengo problemas al querer leer una señal analogica (AnalogRead) y transferirla por medio de SPI a un convertidor Digital analogico DAC MCP4922 el cual es de 12 bits, por tanto es necesario tomar la lectura y enviarla en 2 bytes, pero tengo problemas ya que el samplin (muestreo) esta muy lento necesito mas velocidad.

encontre estos 2 metodos:

/*MCP4922 test
  connections
  ====================================================
  
  +5v           > 4922 pin 1
  Ard pin 10    > 4922 pin 3   (SS - slave select)
  Ard pin 13    > 4922 pin 4   (SCK - clock)
  Ard pin 11    > 4922 pin 5   (MOSI - data out)
  Ground        > 4922 pin 8   (LDAC)
  +5v           > 4922 pin 11  (voltage ref DAC B)
  Ground        > 4922 pin 12
  +5v           > 4922 pin 13  (voltage ref DAC A)
  4922 pin 14 DAC A > 1k resistor > synth CV in
  
 */
 

// MCP4922 DAC out
#define DATAOUT 11//MOSI
#define DATAIN 12//MISO - not used, but part of builtin SPI
#define SPICLOCK  13//sck
#define SLAVESELECT0 10//ss

int valor=0;

void setup() {
  SPI_setup();
 // Serial.begin(9600);
}

void loop() {
valor = analogRead(A0)<< 2;;
 write_note(valor);
}

void write_note(int i) {
  write_valueDACA(i);
}
// **************************************************
// SPI for DAC

void SPI_setup(){

  byte clr;
  pinMode(DATAOUT, OUTPUT);
  pinMode(SPICLOCK,OUTPUT);
  pinMode(SLAVESELECT0,OUTPUT);
  digitalWrite(SLAVESELECT0,HIGH); //disable device

  SPCR = (1<<SPE)|(1<<MSTR) | (0<<SPR1) | (0<<SPR0);
  clr=SPSR;
  clr=SPDR;
  //delay(10);
}

// write out through DAC A
void write_valueDACA(int sample)
{
  // splits int sample in to two bytes
  byte dacSPI0 = 0;
  byte dacSPI1 = 0;
  dacSPI0 = (sample >> 8) & 0x00FF; //byte0 = takes bit 15 - 12
  dacSPI0 |= 0x10;
  
  dacSPI1 = sample & 0x00FF; //byte1 = takes bit 11 - 0
  dacSPI0 |= (1<<5);  // set gain of 1
  
  digitalWrite(SLAVESELECT0,LOW);
  SPDR = dacSPI0;       // Start the transmission
  while (!(SPSR & (1<<SPIF)))     // Wait the end of the transmission
  {};

  SPDR = dacSPI1;
  while (!(SPSR & (1<<SPIF)))     // Wait the end of the transmission
  { };
  digitalWrite(SLAVESELECT0,HIGH);
}

Alguna idea para mejorar la velocidad?? si introduzco una señal de 100Hz no tengo problema al recuperarla igualita, pero si aumento a 200Hz se pierden datos.

el otro codigo es necesario utilizar la libreria MCP4922

#include <MCP4922.h>
#include <SPI.h>
//MCP4922 DAC(51,52,53,5);    // (MOSI,SCK,CS,LDAC) define Connections for MEGA_board, 
MCP4922 DAC(11,13,10,5);    // (MOSI,SCK,CS,LDAC) define Connections for UNO_board, 


void setup()
{

  SPI.begin();
}

void loop()
{
  int i=analogRead(A0)<< 2;;
  
  DAC.Set(i,0);  
}

La respuesta es exactamente la misma :frowning: si tienen alguna idea de transferir a una velocidad mayor por SPI (los pines MISO,MOSI, SCK,SS) lo agradeceria !!

la hoja de datos del DAC MCP4922 tratare de subirla si no, solo es importante que sepan que es un convertidor Digital analogico, de 12 bits y tiene 2 DAC que se pueden utilizar individual y en conjunto.

MCP4922.zip (4.65 KB)

MCP.PNG

Te hago un "Copy/Paste" de la web de Luis Llamas en la que te cuenta un poco la película del SPI

Finalmente, podemos cambiar la velocidad del bus con la función SPI.setClockDivider() divisores de 2 a 128. La frecuencia del bus será la velocidad de reloj dividido por el divisor elegido.

setClockDivider(SPI_CLOCK_DIV2); //8 MHz (considerando un modelo de 16 Mhz)
setClockDivider(SPI_CLOCK_DIV4); //4 MHz
setClockDivider(SPI_CLOCK_DIV8); //2 MHz
setClockDivider(SPI_CLOCK_DIV16); //1 MHz
setClockDivider(SPI_CLOCK_DIV32); //500 KHz
setClockDivider(SPI_CLOCK_DIV64); //250 KHz
setClockDivider(SPI_CLOCK_DIV128); //125 KHz
1
2
3
4
5
6
7

setClockDivider(SPI_CLOCK_DIV2); //8 MHz (considerando un modelo de 16 Mhz)
setClockDivider(SPI_CLOCK_DIV4); //4 MHz
setClockDivider(SPI_CLOCK_DIV8); //2 MHz
setClockDivider(SPI_CLOCK_DIV16); //1 MHz
setClockDivider(SPI_CLOCK_DIV32); //500 KHz
setClockDivider(SPI_CLOCK_DIV64); //250 KHz
setClockDivider(SPI_CLOCK_DIV128); //125 KHz

Sin embargo, estas funciones están obsoletas desde la versión de Arduino 1.6.0., prefiriéndose la función beginTransaction, como muestra el siguiente ejemplo.

SPI.beginTransaction (SPISettings (2000000, MSBFIRST, SPI_MODE0)); // 2 MHz clock, MSB first, mode 0
1

SPI.beginTransaction (SPISettings (2000000, MSBFIRST, SPI_MODE0)); // 2 MHz clock, MSB first, mode 0

échale un ojo aquí a ver si te es de ayuda.

Un saludo!

hola muchas gracias no sabia que eso ya estaba obsoleto, igual debe ser funcional pero se ve mucho mas facil lo que me has publicado oye disculpa entonces con el codigo seria algo asi?? esque no me corre nada en la señal de salida no se si debo activar o desactivar el pin de datos al usar esos comando que me dijiste este es mi codigo

#include <SPI.h>

#define DATAOUT 11//MOSI
#define DATAIN 12//MISO - not used, but part of builtin SPI
#define SPICLOCK  13//sck
#define SLAVESELECT0 10//ss

void setup() {
  
  pinMode(DATAOUT, OUTPUT);
  pinMode(SPICLOCK,OUTPUT);
  pinMode(SLAVESELECT0,OUTPUT);
}

void loop() {

SPI.beginTransaction (SPISettings (2000000, MSBFIRST, SPI_MODE0));  // 2 MHz clock, MSB first, mode 0
int valor = analogRead(A0)<< 2;;
SPI.endTransaction ();
}

actualizo, segun la informacion otorgada el codigo seria algo asi:

#include <SPI.h>

#define DATAOUT 11//MOSI
#define DATAIN 12//MISO - not used, but part of builtin SPI
#define SPICLOCK  13//sck
#define SLAVESELECT0 10//ss

void setup() {
  
  pinMode(DATAOUT, OUTPUT);
  pinMode(SPICLOCK,OUTPUT);
  pinMode(SLAVESELECT0,OUTPUT);

  SPI.begin();
}

void loop() {
SPI.beginTransaction (SPISettings (2000000, MSBFIRST, SPI_MODE0));  // 2 MHz clock, MSB first, mode 0
 digitalWrite(SLAVESELECT0,LOW); //enable device
int valor = analogRead(A0)<< 2;;
SPI.transfer(valor);
 digitalWrite(SLAVESELECT0,HIGH); //disable device
SPI.endTransaction ();
}

pero no me da señal de salida, ni marca error. alguna idea??

Según la hoja de datos, aún la versión de 8 bits necesita que se transfieran dos bytes; o de lo contrario cualquier cambio será cancelado. Hay que ajustar la variable para que todos los bits se acomoden como deben.

En tu caso 12 bits son de datos y 4 son de configuración. En la hoja de datos no mencionan nada de transferencia en serie; así que supongo que es obligatorio mantener los bits de configuración en cada muestra.

Para mejorar el rendimiento, hay que disminuir el prescaler del ADC, usar la frecuencia de reloj más alta que el SPI de Arduino pueda ofrecer. Si el DAC es el único dispositivo en el bus, creo que no sería necesario utilizar endTransaction (pero sí hay que alternar el estado del pin CS en cada muestra; lo más rápido es realizarlo mediante el registro PORTx).

La rutina de colocar la muestra al DAC sería la siguiente:

//Estas constantes se van a utilizar como "platilla" para hacer más fácil la colocación de los bits de configuración 

#define CONFIG_DAC_A 0b0001000000000000 // Sin búfer, ganancia de 1x y canal siempre activo.
// El dígito después de la 'b' déjalo así
#define CONFIG_DAC_B CONFIG_DAC_A | 0x8000


// Ya inicializé el bus con beginTransaction(), eso último puede hacerse una sola vez en el setup
// Asumiendo un Atmega328P; que el pin CS está configurado como salida, que es el 4, y que inicialmente está en estado alto 
PORTD ^= B00010000; // CS en estado bajo
SPI.transfer16(CONFIG_DAC_A | (analogRead(A0) << 2)); // ¿Necesitas para algo los bytes de respuesta?
PORTD ^= B00010000; // CS en estado alto

Si mi propuesta la hallas difícil de entender, pregunta; que de todas formas esto de las operaciones con bits y uso de SPI pueden no ser fácil de digerir.

Actualizo: a menos que quieras una ganancia de 1x y/o utilizar el canal B, no necesitas utilizar ninguna constante. En ese caso la transferencia se reduciría a:

SPI.transfer16(analogRead(A0) << 2); // El DAC no tiene MISO, por lo tanto esta función siempre va a retornar cero

PD: si no conectas a tierra el pin LDAC del DAC, la salida nunca se actualizará.

lucario, de antemano muchas gracias por tu ayuda, estuve utilizando lo que me pasaste del código,
y creo que quedo algo asi: notas puse la velocidad a 20000000, lo del chip select no me dejo hacerlo con el port D yo estoy utilizando CS pin 10 YA ME FUNCIONO LA SEÑAL DE SALIDA!!

#include <SPI.h>

#define DATAOUT 11//MOSI
#define SPICLOCK  13//sck
#define SLAVESELECT0 10//ss

  #define CONFIG_DAC_A 0b0001000000000000 // Sin búfer, ganancia de 1x y canal siempre activo.
// El dígito después de la 'b' déjalo así
#define CONFIG_DAC_B CONFIG_DAC_A | 0x8000

void setup() {
  
  pinMode(DATAOUT, OUTPUT);
  pinMode(SPICLOCK,OUTPUT);
  pinMode(SLAVESELECT0,OUTPUT);

  SPI.begin();
}

void loop() {
SPI.beginTransaction (SPISettings (2000000, MSBFIRST, SPI_MODE0));  // 2 MHz clock, MSB first, mode 0
 digitalWrite(SLAVESELECT0,LOW); //enable device
SPI.transfer16(CONFIG_DAC_A | (analogRead(A0) << 1)); // ¿Necesitas para algo los bytes de respuesta?
 digitalWrite(SLAVESELECT0,HIGH); //disable device
}

lo malo es que me quedo exactamente igual, en el muestreo a este codigo que fue el primero que hice :frowning: que lo tome de un codigo que era para hacer test al MCP4922. entonces sigo en las mismas :frowning: tengo la misma velocidad de muestreo a los 100HZ veo la señal identica pero le subo a 200 -300 y mas y se descompone se pierden muestras se ve algo como la señal de la imagen que subi

alguna idea?? lo agradecere eternamente ya tengo como 4 codigos que hacen esto exactamente, incluso usando librerias que han hecho pero no logro obtener un sampling mayor.

 /*MCP4922 test
  connections
  ====================================================
  
  +5v           > 4922 pin 1
  Ard pin 10    > 4922 pin 3   (SS - slave select)
  Ard pin 13    > 4922 pin 4   (SCK - clock)
  Ard pin 11    > 4922 pin 5   (MOSI - data out)
  Ground        > 4922 pin 8   (LDAC)
  +5v           > 4922 pin 11  (voltage ref DAC B)
  Ground        > 4922 pin 12
  +5v           > 4922 pin 13  (voltage ref DAC A)
  4922 pin 14 DAC A > 1k resistor > synth CV in
  
 */
 

// MCP4922 DAC out
#define DATAOUT 11//MOSI
#define DATAIN 12//MISO - not used, but part of builtin SPI
#define SPICLOCK  13//sck
#define SLAVESELECT0 10//ss

int valor=0;

void setup() {
  SPI_setup();
 // Serial.begin(9600);
}

void loop() {
valor = analogRead(A0)<< 2;;
 write_note(valor);
}

void write_note(int i) {
  write_valueDACA(i);
}
// **************************************************
// SPI for DAC

void SPI_setup(){

  byte clr;
  pinMode(DATAOUT, OUTPUT);
  pinMode(SPICLOCK,OUTPUT);
  pinMode(SLAVESELECT0,OUTPUT);
  digitalWrite(SLAVESELECT0,HIGH); //disable device

  SPCR = (1<<SPE)|(1<<MSTR) | (0<<SPR1) | (0<<SPR0);
  clr=SPSR;
  clr=SPDR;
  //delay(10);
}

// write out through DAC A
void write_valueDACA(int sample)
{
  // splits int sample in to two bytes
  byte dacSPI0 = 0;
  byte dacSPI1 = 0;
  dacSPI0 = (sample >> 8) & 0x00FF; //byte0 = takes bit 15 - 12
  dacSPI0 |= 0x10;
  
  dacSPI1 = sample & 0x00FF; //byte1 = takes bit 11 - 0
  dacSPI0 |= (1<<5);  // set gain of 1
  
  digitalWrite(SLAVESELECT0,LOW);
  SPDR = dacSPI0;       // Start the transmission
  while (!(SPSR & (1<<SPIF)))     // Wait the end of the transmission
  {};

  SPDR = dacSPI1;
  while (!(SPSR & (1<<SPIF)))     // Wait the end of the transmission
  { };
  digitalWrite(SLAVESELECT0,HIGH);
}

¿Probaste iniciando la transacción sólo en el setup?

#include <SPI.h>

#define DATAOUT 11//MOSI
#define SPICLOCK  13//sck
#define SLAVESELECT0 10//ss

  #define CONFIG_DAC_A 0b0001000000000000 // Sin búfer, ganancia de 1x y canal siempre activo.
// El dígito después de la 'b' déjalo así
#define CONFIG_DAC_B CONFIG_DAC_A | 0x8000

void setup() {
  
  pinMode(DATAOUT, OUTPUT);
  pinMode(SPICLOCK,OUTPUT);
  pinMode(SLAVESELECT0,OUTPUT);
  digitalWrite(SLAVESELECT0,HIGH); // Pon SS en estado alto de antemano.
  SPI.begin();
  SPI.beginTransaction (SPISettings (2000000, MSBFIRST, SPI_MODE0));  // 2 MHz clock, MSB first, mode 0
  // La inicialización ocurre una sola vez a menos que otra librería necesite utilizar el bus SPI.

}

void loop() {
  PORTB ^= B00000100; // Se usa el puerto B ya que tienes el SS con el pin 10
  SPI.transfer16(CONFIG_DAC_A | (analogRead(A0) << 1)); // ¿Necesitas para algo los bytes de respuesta?
  PORTB ^= B00000100;
}

Si esto aún no te satisface, el último recurso sería no utilizar las constantes y transmitir solo la lectura analógica (pero sigue siendo con transfer16).

muchas gracias Lucario si pudiera darte 1000 karmas jaja te los mereces, obtuve muy buenos resultados.

Aprovecho para invitar a cualquier persona que quisiera caracterizar el DAC MCP4922 para sus proyectos, a preguntar con confianza ya lo tengo bien medido todo por si no entienden algo de lo que se haya dicho en este post.