Error extraño al interactuar con el registro de turnos (CPLD) a través de SPI

Implementé un registro de desplazamiento de salida en serie (PISO) paralelo de 8 bits en VHDL en mi Max V CPLD. Estoy usando SPI para interactuar con el CPLD usando mi AVR. El circuito funciona pero solo parcialmente. Supongamos que tengo una entrada que contiene solo un bit establecido, es decir, 00010000 o 10000000, etc. En este caso, el AVR lee los datos correctamente.

Sin embargo, si la entrada al registro PISO es 10100000, el AVR lee los datos como 11100000. Esto también se refleja en mi osciloscopio. Adjunto la imagen para ilustrar esto.

ingrese la descripción de la imagen aquí

La forma de onda superior es la salida en serie de mi registro de desplazamiento y la forma de onda inferior es el reloj. ¿No debería la salida en serie convertirse en 0 en el segundo ciclo de reloj?

Si los datos son 10000001, entonces se leen como 10000011. ¿Qué podría estar causando esto? Pensando que podría ser mi código VHDL, incluso copié y pegué los códigos de otras personas de la web para ver si funciona, pero desafortunadamente obtengo los mismos resultados.

El siguiente es el código para mi AVR

int main(void)
{
DDRB = (1 << DD_MOSI) | (1 << DD_SCK) | (1 << 2) | (1 << 0);

SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR0)  | (1 << SPR1);
DDRD = 0xFF;
PORTD = 0x00;

char datain;

while(1)
{
    PORTB = 1; // This loads the shift register.
    _delay_ms(10);
    PORTB = 0x00;
    _delay_ms(10);

    SPDR = 0b01011101;

    while(!(SPSR & (1 << SPIF)));
    datain = SPDR;


    _delay_ms(10);

    PORTD = datain;
}


return 1;
}

Y este es el código VHDL que estoy usando. Tenga en cuenta que he omitido intencionalmente la entrada. Estoy codificando la entrada ya que entonces no tengo que perder el tiempo con cables adicionales. Lo cual esperaba proporcionaría menos margen de error.

library ieee; 
use ieee.std_logic_1164.all; 
entity PISO is 
port(C, CS_N : in std_logic; 
  --D   : in std_logic_vector(7 downto 0); 
   SO  : out std_logic); 
end PISO; 
architecture archi of PISO is 
signal tmp: std_logic_vector(7 downto 0); 
begin  
process (C, CS_N) 
   begin 
     if CS_N = '1' then 
         tmp <= "10000001"; 
     elsif rising_edge(C) then 
         tmp <= tmp(tmp'high - 1 downto tmp'low) & '0'; 
     end if; 
end process; 
SO <= tmp(7); 
end archi;

Tenga en cuenta que soy bastante nuevo en VHDL/CPLD y sigo encontrando algo con respecto a los tiempos y cuán críticos son. He intentado leer sobre ellos, pero parece que no lo entiendo del todo. Entiendo los retrasos en las puertas, etc., pero ¿cómo se supone que debo configurar el CPLD para que esto no sea un problema? Mi reloj no es muy rápido. Lo estoy ejecutando en la frecuencia más baja posible, es decir, 62,5 kHz. ¿Podría este error tener algo que ver con las placas de prueba? Me he quedado sin revestimientos de cobre fotorresistentes, por lo que todavía no puedo hacer una PCB. El Max V está en una placa de desarrollo.

Agradeceré cualquier tipo de sugerencia sobre cómo resolver este problema. Estuve en eso durante las últimas 8 horas más o menos sin éxito.

INFORMACIÓN ADICIONAL: Inicialmente, cuando programé el CPLD, lo probé mediante los botones de mi placa de desarrollo. Eso no mostró ningún problema y el registro escupió bits en serie muy bien. No solo eso, incluso lo he cronometrado a través del AVR. Eso funcionó muy bien también. Solo después de confirmar que funcionaba bien, comencé a trabajar en la interfaz SPI, que es donde las cosas salieron mal.


Nuevas formas de onda:

Está bien, aquí vamos. Para todas las capturas de pantalla, la salida de serie fue la misma (10000001). Hice una ligera mejora, de modo que 10000001 ya no aparece como 10000011 sino como 10000010. Cuando estaba depurando antes, inadvertidamente configuré el bit CPHA en 1. Cuando lo configuré de nuevo en 0, los datos eran 100000010 en lugar de 10000011 Todavía no es correcto, por supuesto. Aquí están las formas de onda del osciloscopio.

ingrese la descripción de la imagen aquí

Forma de onda superior: Salida serie

Forma de onda inferior: Reloj. Activado en el flanco ascendente.

ingrese la descripción de la imagen aquí

Forma de onda superior: Chip Select (activo bajo)

Forma de onda inferior: Reloj. Activado en el flanco ascendente.

ingrese la descripción de la imagen aquí

Forma de onda superior: Chip Select

Forma de onda inferior: Reloj. Activado en el borde descendente.

ingrese la descripción de la imagen aquí

Forma de onda superior: Salida serie.

Forma de onda inferior: Reloj. Activado en el borde descendente.

EDITAR: Olvidé agregar, este es mi código AVR limpio ahora.

while(1)
{
    bit_clear(PORTB,BIT(CS)); // Low Chip Select
    SPDR = 0xFF;
    while(!(SPSR & (1 << SPIF)));
    datain = SPDR;
    PORTD = datain;
    bit_set(PORTB,BIT(CS)); // High Chip select
    _delay_us(100); // Keep high for 100us. This made it easier to see it on o-scope.
}
Como dijiste que funcionó antes, me pregunto en qué borde el AVR registra los datos SPI. Si el AVR registra datos en el flanco ascendente, debe cambiarlos en el flanco descendente. Cambiar mientras se lee hará cosas raras como esa.
Toda esta charla sobre lo que ve el AVR es una pista falsa que distrae y hace perder el tiempo. Si los datos son incorrectos en el alcance, entonces es claramente el remitente y no el receptor el que tiene el problema. Por lo tanto, olvídese del AVR y corrija su código CPLD.

Respuestas (1)

Bueno, echemos un vistazo a su código VHDL. Tenga en cuenta que no estoy tratando de ser crítico o duro, solo estoy tratando de ponerlo al día rápidamente. Su código actualmente es este:

process (C, ALOAD) 
begin 
  if (ALOAD='1') then 
    tmp <= "10000001"; 
  elsif (C'event and C='1') then 
    tmp <= tmp(6 downto 0) & '0'; 
    SO <= tmp(7);  
  end if;     
end process; 

Pero debería verse así:

process (C, ALOAD)
begin
  if ALOAD='1' then 
    tmp <= "10000001";
  elsif rising_edge(C) then
    tmp <= tmp(tmp'high-1 downto tmp'low) & "0";  -- '0' and "0" are equivalent in this context
  end if;
end process;

SO <= tmp(tmp'high);

Los cambios que hice son menores, sin duda, pero importantes.

Lo primero es mi uso de "rising_edge()". Esto es algo nuevo para VHDL y no está cubierto en algunos de los libros de VHDL. Esto se considera mejor que usar 'event. Del mismo modo, hay un borde cayendo ().

Lo siguiente es mi uso de 'alto y' bajo en lugar de codificar los valores. Esto realmente no importa para este código, pero cuando comiences a hacer cosas más grandes, te ayudarán mucho. Por ejemplo, podría simplemente cambiar la definición de tmp para que sea más grande y el resto del código se ajustará automáticamente (excepto la inicialización a "10000001").

También debo señalar que se desaconseja un reinicio asíncrono o una carga en FPGA , pero está bien en CPLD.

También tenga en cuenta que moví la asignación de SO fuera del proceso. Esto puede o no ser lo que pretendías. De la forma en que lo tiene, hay un flip-flop adicional que va de tmp (7) a SO. Normalmente, con SPI, esto no es lo que desea porque el reloj SPI podría desaparecer al final de la transferencia y nunca obtendrá ese último bit. Por otro lado, con la forma en que lo hice, comenzará a obtener ese primer bit cuando ALOAD = '1', no en el borde ascendente de C.

Desafortunadamente, esto realmente no responde a su pregunta sobre por qué está recibiendo datos incorrectos en el AVR. Simplemente no hay suficiente información en su pregunta. Este es el tipo de cosas que miraría o me preocuparía:

Su imagen o-scope no muestra las otras señales spi, como CS. CS es fundamental para comprender dónde se supone que deben ir los bits. Además, saber cuál es el modo SPI ayudará con esto.

En la imagen del oscopio, el primer ciclo de reloj donde SO='1' NO es el primer bit de la transferencia SPI. Cargó el registro de desplazamiento 10-20 mS antes de eso, y su período de reloj es de aproximadamente 20 uS. Entonces tenía al menos 1 ciclo de reloj antes de SO='1', y probablemente más. Entonces, aquí están sucediendo algunas cosas extrañas: no tenemos suficiente información para comprender el comportamiento.

Está usando ALOAD para cargar el registro de desplazamiento, pero normalmente usaría CS_N (bajo activo). Mientras que CS_N='1' realiza una carga asíncrona del registro de desplazamiento, mientras que CS_N='0' lo desplaza. Usar ALOAD como el que tiene aquí está bien, pero probablemente no es lo que quería y no funciona con lo que parece ser un reloj SPI extraño (del punto anterior).

Entonces, esto es lo que debes hacer...

Limpie un poco el VHDL. Vuelva a publicar la versión actualizada. Dado que su alcance solo tiene 2 canales, conecte un canal a CS_N y el otro canal a CLK. Gatillo en el flanco descendente de CLK. Capture una forma de onda que muestre 2 relojes antes del flanco descendente de CLK y 5 relojes después. Sin cambiar la configuración del osciloscopio , elimine CLK y coloque la sonda en SO. Captura otra imagen. Repita esto para el flanco ascendente de CLK. Entonces, 4 imágenes de forma de onda en total.

Haz eso y podemos reevaluar lo que podría estar mal.

Editar: Actualizado para reflejar la pregunta actualizada.

Veo dos problemas: primero, si el AVR está muestreando en el flanco ascendente del reloj, debe marcar su registro de desplazamiento fuera del flanco descendente. Como mencionó Supercat, esto le dará +/- 0.5 períodos de reloj de configuración y retención en su AVR.

Y segundo: como mencionaste, estás obteniendo 10000010 en lugar de 10000001. No creo que tu código VHDL sea erróneo en este, pero obviamente está saliendo mal del CPLD. Si tuviera que adivinar, diría que el problema se debe a algunos problemas de integridad de la señal con su CLK. Es difícil saberlo con su alcance, pero parece que tiene más de un voltio de sobreimpulso y subimpulso en esa señal (y con eso vendría mucho zumbido). Ese timbre, si es lo suficientemente malo, podría hacer que el CPLD "doble el reloj", lo que significa ejecutar el registro de desplazamiento dos veces para un solo borde del reloj. Y si es _realmente_malo_ podría hacer que el CPLD se enganche y explote literalmente (lo he visto suceder).

Aquí hay algunos experimentos para probar:

  1. En lugar de 10000001, use 10101010 o 01010101. Esto lo ayudará a ver qué bit se duplica y si siempre es el mismo bit.

  2. Acérquese a los bordes del reloj con el visor. Asegúrese de que sus sondas de alcance estén en el CPLD, no en el AVR, cuando haga esto. Sí, hace una GRAN diferencia.

Asumiendo que hay un sobreimpulso o un subimpulso y suena en el reloj, la solución es agregar una terminación de señal adecuada en la línea. Comenzaría con una resistencia en serie de 50 ohmios en el AVR. Nota: esto ralentizará los bordes del reloj, pero como está cronometrando el CPLD en el borde descendente, tiene mucho tiempo disponible.

¿Cómo funciona el reloj de AVR a CPLD? ¿Un cable largo entre PCB? Esa sería mi conjetura.

Muchas gracias David. El libro que tengo es viejo y no cubre las cosas nuevas de las que hablaste. Puedo ver la ventaja de usar atributos 'altos/'bajos. Del mismo modo, el borde ascendente también limpia las cosas y mejora la legibilidad. Sin embargo, estoy confundido acerca de CS. El CPLD es el único dispositivo que he conectado al bus SPI (además del programador ISP) y como no selecciono ningún chip, configuré CS como salida pero no estoy haciendo nada con él. ¿Mi próximo paso es conectarlo de manera que cargue el registro?
Además, he dejado el modo SPI por defecto. Dado que el CPLD cambia en el borde ascendente, pensé que el modo predeterminado funcionaría. ¿Estoy equivocado con respecto a esto?
@Saad SPI Chip select hace más que solo "seleccionar el chip". Enmarca toda la transferencia SPI (dice cuándo comienza y termina). De lo contrario, todo lo que obtiene es una secuencia de bits y relojes y no sabe cuándo comienza un byte/palabra/transferencia y finaliza el otro. Algunas aplicaciones no "necesitan" un CS, pero en la mayoría de los casos nos dirá lo que espera el AVR. El modo SPI define dos cosas: la polaridad del reloj y la temporización del CS. Estos son importantes si está tratando de averiguar qué está haciendo o esperando el AVR.
Es común en SPI que los dispositivos desplacen los datos en un borde del reloj y bloqueen los datos en el otro. Esto generalmente permite un sesgo de +/- medio reloj entre el reloj y las líneas de datos. Algunos dispositivos de hardware (p. ej., 74HC595 y 74HC165) siempre bloquean los datos en el mismo borde del reloj cuando cambian su salida, y algunos dispositivos SPI permiten que los bordes del reloj de transmisión y recepción se configuren de forma independiente. Si un esclavo emite el primer bit en /CS y los bits subsiguientes con cada flanco de reloj inicial, un maestro que genera el reloj podrá tolerar un reloj completo de retraso de ida y vuelta en lugar de 1/2 reloj.
@David Kessner: Me pregunto por qué no he visto muchos dispositivos SPI-ish usar algún medio de encuadre que no sea un cable /CS. Con SPI, generalmente no habrá más de dos bordes de MISO entre los bordes del reloj. Si MISO cambia varias veces mientras el reloj permanece en un estado, tales transiciones en exceso podrían indicar el inicio de un paquete. Hay muchas transiciones posibles que uno podría usar.
@David Kessner: actualicé mi pregunta original con las nuevas formas de onda. Espero que sean lo suficientemente claros.
@supercat SPI es un protocolo simple, que es fácil de implementar en una pequeña cantidad de lógica. Parte de esa simplicidad es el cronometraje básico. Hacer lo que propones haría que la lógica de la interfaz fuera mucho más compleja. En ese momento, también podría usar I2C. Saad, veré tu actualización más tarde, cuando no esté escribiendo en mi iPhone.
@David Kessner: Cierto, SPI es simple, pero la cantidad de lógica necesaria para manejar el encuadre sin /CS es bastante minúscula. Por ejemplo, se podría diseñar un dispositivo que se pueda usar con /CS atado bajo agregando dos flip flops y una puerta "OR". Ambos flip flops tienen reinicio asíncrono vinculado a CLK y reloj vinculado a MOSI. FF0 tiene datos atados alto y FF1 tiene entrada de datos asociada a FF0. La salida de FF0 se combina con OR con /CS para producir el CS efectivo.
@saad Mi respuesta ha sido actualizada.
@David Kessner: la resistencia de 50 ohmios ayudó. 10000001 sale correctamente. Pero 10000011 sale como 11000111. Así que no he podido resolver el problema por completo. Aquí hay una cosa que noté: la frecuencia del reloj no es muy constante. Cambia de 61,3 kHz a 61,9 kHz y, a veces, incluso supera los 62 kHz. ¿Podría esto causar problemas como los que estamos viendo? Incluso cuando se dispara el osciloscopio y la forma de onda está sentada en la pantalla, puedo verla parpadear y moverse hacia la derecha y luego volver rápidamente a su posición original. Esto sucede constantemente.
No estoy usando un cristal externo sino que confío en el reloj interno de la MCU. ¿Tener un cristal externo ayudaría en este problema? En segundo lugar, ¿crees que si construyo el mismo circuito en una veroboard/perfboard (sin placas de cobre en este momento), ayudaría? Aunque el CPLD está en una placa diferente, puedo asegurar que los cables son más cortos de lo que son ahora. Además, ¿por qué el reloj de la MCU se sobrepasaría tanto?
@Saad Los cambios de frecuencia no deberían importar ya que no está violando los tiempos de configuración/retención. Prueba con 100 ohmios. También verifique la integridad de la señal de las otras señales, ya que es probable que estén en mal estado. Recuerde que la resistencia en serie va al controlador, no al receptor. La longitud del cable no debería ser un problema, excepto que magnifica otros problemas como la terminación de la señal y los desajustes de impedancia.
Las señales se ven bien a mis ojos en el CPLD. Sobreimpulso inferior a 0,2 V. No estoy seguro si eso es aceptable en los sistemas. Por mi parte, estoy al final de mi ingenio. Probé 180 ohmios, por cierto, y no pareció hacer una diferencia.