Configuración de registros ADC mediante comunicación spi

Soy nuevo en microcontroladores: estoy tratando de leer valores de ADC externos de un ADC AD7798 usando comunicación SPI.

Inicialmente, tengo que configurar algunos registros ADC; algunos registros no están configurados. Para configurar los registros, tengo que usar el registro de comunicación para seleccionar qué registro quiero configurar.

Por ejemplo, quiero configurar el registro de configuración AD7798 (16 bits). Tengo un código como este: #include #define ADC_CS PORTB.3 #define WG_CS PORTB.4 #define MOSI PORTB.5 #define MISO_PU PORTB.6 #define MISO_PIN PINB.6 #define SCK PORTB.7

//global functions.
unsigned int adcConfig;
unsigned int adcMode;
unsigned int adcId;

void init_io(void) 
{ 
DDRB = 0xBF;        // make SCK, MOSI, CS1, CS2 outputs 
ADC_CS = 1;              //disable ADC 
WG_CS = 1;               //disable WaveGenerator 
MISO_PU = 1;             //enable pull-up on MISO so we can test !RDY 
} 

unsigned char spi(unsigned char data) 
{ 
//Start transmision 
SPDR = data; 
//Wait for transmision complete 
while (!(SPSR & (1<<SPIF))); 
return SPDR; 
} 

//Sets the waveform generator output to given phase 
void SetWGPhase(unsigned int phase) 
{ 
SPCR = 0x5A; // mode #2 F_CPU/64 
WG_CS = 0;                      // enable 
spi(0x20); 
spi(0x00); 
spi((phase >> 8) | 0xC0);       //Load into phase register 0 
spi(phase & 0x00FF); 
WG_CS = 1; 
} 

void setupAd(){ 
    SPCR = 0x5D; 
    ADC_CS = 0; 
   // while(spi(0x10) != 0x10); 
    spi(0x10);                  //set up communication register for configuration reg. 
    spi(0x07);        
    spi(0x10); 

    spi(0x08);                  //set up communication register for mode reg. 
    spi(0x00);        
    spi(0x0A); 
    ADC_CS = 1; 
    } 


 unsigned int ReadAd(void) 
 { 
unsigned int data; 
SPCR = 0x5D; // mode #3 F_CPU/16 
CheckStatus();
ADC_CS = 0;                     // enable 
while (MISO_PIN != 0) ;         // wait for  DOUT/!RDY line to go low 
//Read data 
spi(0x58);                      //Place readinstruction in communication register 
data = spi(0xFF);               // read hi-byte 
data = (data << 8) | spi(0xFF); // and lo-byte. 
ADC_CS = 1;                     // disable 
return data; 
} 

 unsigned char CheckStatus(void)
{
char adcStatus; 
            SPCR = 0x5D;
            ADC_CS = 0;                     // enable  
            while(ADC_CS_PIN);
            adcStatus = 0xFF; 
while(!(adcStatus & 0x80)){                                     

             spi(0x40);
             adcStatus = spi(0xFF); 
          }          
ADC_CS = 1;                      

return adcStatus;
}

unsigned int ReadAdConfReg(void) 
{              
  unsigned int retvalconfig;
SPCR = 0x5D;  
ADC_CS = 0;      
while (MISO_PIN != 0) ; 
spi(0x50); 
adcConfig = spi(0xFF);    
adcConfig = (adcConfig << 8) | spi(0xFF); 
retvalconfig= adcConfig;
ADC_CS = 1; 
return retvalconfig;
} 

unsigned int ReadAdModeReg(void) 
{              
  unsigned retvalmode;
SPCR = 0x5D;  
ADC_CS = 0;        
while (MISO_PIN != 0) ; 
spi(0x48); 
adcMode = spi(0xFF);  
adcMode = (adcMode << 8) | spi(0xFF); 
retvalmode =adcMode;   
ADC_CS = 1;
return retvalmode;
} 
unsigned int ReadAdIdReg(void) 
{              

SPCR = 0x5D;  
ADC_CS = 0;          
while (MISO_PIN != 0) ; 
spi(0x60); 
adcId = spi(0xFF);    
ADC_CS = 1;
 return adcId; 
} 

cuando imprimo el registro de configuración, está dando el valor "16383". pero cuando apago/encendo el objetivo, obtengo "1808 (que es equivalente a 0x0710)" y luego da el mismo valor que "16383". También probé con diferentes configuraciones, pero no cambia, siempre imprime "16383", excepto apagar/encender. Creo que es el valor predeterminado.

Incluso con el registro de modo, siempre está imprimiendo "10 (que es equivalente a 0x000A)", pero ese es el valor que obtengo siempre, incluso si cambio la configuración a "0x0022".

Incluso he intentado leer el registro de identificación, pero está dando "0x48". pero en la hoja de datos menciona "0xX8" para AD7798. Gracias de antemano.

Alguien que me ayude por favor, no tengo idea de qué error estoy haciendo aquí.

Descubrirá que los usuarios están más dispuestos a responder si obtienen toda la información relevante posible, como un enlace a la hoja de datos del ADC.
¿Qué se supone que debe hacer spi(0x07)<<8? La parte <<8 no tiene ningún efecto aquí.
Usted dijo en un comentario sobre una pregunta anterior que verificó que la forma de onda sea correcta con un osciloscopio. Ahora no estoy tan seguro. ¿Puedes publicar una captura de pantalla del 'alcance o analizador lógico?
@Rocketmagnet Lamento mucho el comentario anterior. Lo he comprobado mal. Lo revisé una vez más después de su sugerencia, así que encontré que había una diferencia en la señal del reloj y que mi registro de configuración no estaba configurado. He hecho algunas modificaciones en el código anterior, pero tengo que probar esto y lo editaré allí.
@verendra: si agrega la etiqueta 'c' a su pregunta, el código se resaltará automáticamente.

Respuestas (4)

Si bien esta hoja de datos me está haciendo sangrar los ojos con la complejidad de hacer una conversión/lectura simple, debe realizar algunos cambios simples.

No estoy seguro de por qué la gente está haciendo esto más difícil de lo que realmente es. Tira de la línea de selección de chip hacia abajo, y el IC de destino simplemente debería leer lo que le dé hasta que lo vuelva a subir. El hecho de que su método tome un valor de 8 bits no significa que no pueda llamarlo dos veces para pasar un valor de 16 bits a su IC. No hay necesidad de golpear nada. Eso es una tontería.

Llame a su método SPI para decirle al ADC que desea escribir en el registro de configuración:

spi(0x10);

Según entiendo de la hoja de datos, puede escribir inmediatamente su valor de 16 bits en el registro de configuración después de eso, por lo que haría:

spi(0x07);
spi(0x10);

Olvidé de qué manera terminará siendo ensamblado en el lado IC de destino, por lo que simplemente podría invertirlos si las cosas no funcionan bien. No hay necesidad de cambiar de bit ningún valor en absoluto. En el peor de los casos, debe volver a subir la línea CS antes de volver a bajarla para enviar los datos para escribir en el registro de configuración.

De lo contrario, esto es súper simple. Recuerde, si el dispositivo SPI de destino espera más de un byte, es decir, un valor de 16 bits, es muy probable que envíe los bytes en el orden en que aparecerían normalmente (por lo que si desea enviar 0x1234, tendría 0x12 y 0x34) funcionará bien y no necesitará cambiar nada.

Si el controlador SPI en el microcontrolador es de 8 bits (muy probable), luego de 8 bits se liberará el SS, como usted dice, lo que hará que los datos se bloqueen. Los siguientes 8 bits arreglarán eso: cuando están bloqueados, la palabra de 16 bits está completa, pero el ADC procesará el primer bloqueo y probablemente se verá como basura, o algo peor. Bit-banging puede ser una solución.
Eso es si liberas el SS... que no tienes que hacerlo. He realizado transferencias de 16 bits en un LPC1769 con un controlador SSP de 16 bits y en un viejo Arduino Uno normal con, presumiblemente, un controlador SPI de 8 bits. El control de las SS funciona según lo previsto en ambos casos. Tire hacia abajo. Envía 16 bits. Tiralo alto. Voilá. Esto realmente parece que se está convirtiendo en esta solución locamente difícil, pero no hay forma de que todos los que tienen un ATmega estén golpeando SPI solo para hacer una transferencia de 16 bits. :P La clave es controlar la línea SS tú mismo.
si, tienes toda la razón. Por un momento pensé que el SS estaba controlado por el controlador SPI, pero eso es imposible porque no sabe cuál de las 57 :-) líneas SS debe alternarse. Estoy corregido: no se necesitan golpes de bits. (Con el NXP LPC17xx no hay problema de todos modos, ya que su longitud SPI FIFO es seleccionable por el usuario).
Bien. Controlo el SS manualmente en mi caso por esta misma razón... Sé mejor que el controlador lo que quiero enviar y recibir. Esa fue la base de todo mi argumento: contrólalo manualmente y serás dorado. Lo siento si eso no se entendió claramente. :)
Pero OP tendrá que modificar su spi()función entonces. Entre llamadas, nunca deshabilita SS, por lo que será automático en la función. Tal vez podría agregar un argumento opcional hold_ss.
Parece que tiene ChipSelectAd() para alternar la línea SS para el ADC específicamente. Solo necesita anular y reafirmar antes de enviar el contenido real del registro.
@Tody: tiene razón sobre la ChipSelectAd()función, pero se equivoca al anularla entre un comando y los datos asociados.
Me parece bien. Realmente no me preocupaba cómo el ADC tomaba sus datos para una operación, pero me preocupaba más señalar que alimentarlo con 16 bits era estúpidamente simple y no complicado. :D
@TobyLawrence Aquí estoy usando el controlador ATmega32-A, ¿puedo necesitar un poco de golpe? No sé sobre eso. Puedo intentar como de costumbre como usted sugirió.
El ejemplo de código de @verendra Ben podría ser una implementación más correcta, pero no debería necesitar golpear nada. Ya tienes todos los métodos necesarios para hacerlo correctamente. :)
@TobyLawrence Sigue siendo el mismo problema, ¿puedes echar un vistazo a mi código editado?

La línea de selección de chip en el AD7798 está activa en nivel bajo. Parece que tienes la polaridad al revés en tu código.

Debe configurar CS en bajo (0) al comienzo de la transferencia y en alto después de la transferencia. Así que prueba esto:

void setupADC()
{
    ChipSelectAd(0); // was 1
    spi(0x10);
    spi(0x07);
    spi(0x10);
    ChipSelectAd(1); // was 0  
}

También verifique que esté usando el modo SPI correcto (llamado CPOL y CPHA en muchos microcontroladores, por "polaridad de reloj" y "fase de reloj"). Esto determina qué borde del reloj activa las transiciones de datos y qué nivel tiene el reloj entre transacciones (durante la inactividad).

El AD7798 requiere CPOL=1 y CPHA=1 (modo SPI 3).

Sigue siendo el mismo problema, ¿puedes echar un vistazo a mi código editado?
@verendra: Oh, está bien, parece que ChipSelectAdestá realizando la inversión (es por eso que ayuda a mostrar más código). ¿Qué procesador estás usando? (añadir esto a la pregunta)
Puedes echarle un vistazo a mi nuevo post pero estaba cerrado. allí he agregado imágenes de osciloscopio para verificar las polaridades del reloj y la selección de chips.
@verendra: En esos trazados de osciloscopio, el tiempo se ve bastante bien pero los datos no son 0x10, 0x07, 0x10. ¿Alguna idea de por qué? ¿Es esta una captura de un comando diferente? Además, diga en qué orden están las capturas (claramente, el rastro superior en cada uno es el reloj, pero ¿son MOSI, MISO, CS?)
Esas imágenes no están relacionadas con la configuración del ADC. Esas imágenes están relacionadas con la lectura de la configuración del registro del ADC. Probé Clock con CS, MOSI, MISO mientras leía ADCsetup. parece correcto Pero estoy preguntando cómo verificar la configuración del registro ADC.

Como dice RocketMagnet

spi(0x07)<<8;   

no tiene sentido. Aunque la función spi()está definida char, la está desplazando hacia la izquierda y luego la descarta.

spi(0x07 << 8);   

desplaza el argumento 8 bits a la izquierda, por lo que será 0x0700. Pero eso no hace lo que quieres. No anule la afirmación de SS (Slave Select) entre las llamadas de la spi()función, por lo que será automático. Lo cambiaría para que tenga un argumento opcional hold_ss, de modo que pueda cambiar el primer byte, luego el segundo, y solo luego anular la afirmación de SS.

De esa forma podrá transferir 16 bits de datos al ADC, sin tener que transferir datos antes de la spi()llamada. Alternativamente, puede escribir una función spi16():

unsigned int spi16(unsigned int data16)
{ 
    assert_SS(IOpin);          
    spi8(data >> 8); /* assuming LSB is shifted first */
    spi8(data);
    deassert_SS(IOpin);
}

(Estoy ignorando los datos entrantes por el momento).

La hoja de datos indica que el Registro de configuración realmente necesita 16 bits, pero aunque no brinda detalles, esperaría que los necesite como una sola transferencia. Verifique la spi()función del compilador de su microcontrolador y la hoja de datos del microcontrolador para ver si las transferencias de 16 bits son realmente posibles en primer lugar. Si el controlador puede hacer transferencias de 16 bits, debería poder hacer esto:

spi(0x0710);

Si solo son posibles las transferencias de 8 bits, creo que tendrá que recurrir a un SPI de bits (que no es tan difícil).

editar
Rocketmagnet me señala la definición de la spi()función:

char spi(char data) 

Eso lo dice todo: el argumento es de tipo char, por lo que solo es de 8 bits.

( Gracias por el puntero (sin juego de palabras), Rocketmagnet )

Consulte su pregunta anterior para conocer la definición de char spi (char data).
Ya controló el pin de selección de chip en los momentos apropiados, aunque quizás en los niveles incorrectos.
@BenVoigt Estoy controlando exactamente el pin de selección de chip. Ahora echa un vistazo a mi código editado.

Que micro estas usando? No proporcionó información sobre lo que está spi()haciendo allí. Podría estar enviando 8 bits. En cuyo caso, su cambio de 8 bits resulta en una tontería. Ahora, indicó que desea enviar "0x0710". Piense por un segundo lo que puede estar sucediendo en su código.

Parece estar confundido acerca de cómo se lleva a cabo esta transferencia SPI.

Primero, estás enviando 0x10. Entonces, suponiendo que su spi()función envíe 16 bits, está enviando 0x0700. Acabas de transferir 0x100700.

Estoy bastante seguro de que su spi()función, de hecho, no envía sus 16 bits. Básicamente, estás enviando dos bytes: 0x10 y 0x00. Bien hecho.

Debe invertir el orden de los bytes al enviar varios bytes a través de SPI. Primero, envíe 0x07, y solo luego envíe 0x10.