La transmisión ATMega168 SPI no se iniciará

Guión

Tengo un ATMega168 usando un cristal externo de 10 MHz. El bit del fusible DIV/8 no está configurado. El procesador toma las salidas de 4 codificadores rotatorios de cuadratura. Su información de dirección de rotación se decodifica usando interrupciones de cambio de pin y los estados de pin. La información de rotación se convierte en códigos de control y se envía a través de SPI a otro procesador que maneja algunos motores.

Problema

En el código enumerado, la información de la dirección de rotación se descodifica y convierte en códigos de control según lo diseñado, y se ha verificado en el hardware. La transmisión SPI no se inicia. Mirando los pines SPI en el alcance, el pin !SS permanece alto y el SCK y MOSI permanecen bajos.

Otras dos personas han mirado este código además de mí. Sé que tengo que estar pasando por alto algo simple. ¿Por qué no se inicia la transmisión?

/*Ports:
D.7..D.0 - 4x quadrature rotary encoders, triggering pin change interrupts
B.5 - SCK (SPI clock line)
B.3 - MOSI(SPI data line)
B.2 - !SS (SPI chip select)

This module interfaces with the rotary encoders, converts their 
rotation to control commands, and transfers these commands over 
the serial peripheral interface to the motor controller.
*/

#define LASER_X_L 28
#define LASER_X_R 56
#define LASER_Y_D 84
#define LASER_Y_U 112
#define MIRROR_X_L 140
#define MIRROR_X_R 168
#define MIRROR_Y_D 196
#define MIRROR_Y_U 224
#define NOTHING 0

#include <avr/io.h>
#include <avr/interrupt.h>

void initPorts(void); //set up function for GPIO ports
void initExtInt(void); //set up function for external interrupts
void initSPI(void); //set up function for SPI

int main(void)
{
    initPorts(); //call port set up function
    initExtInt(); //call interrupt set up function
    initSPI(); //call SPI set up function

    while(1)
    {

    }
}

void initPorts(void)
{
    DDRB = 0b00101100; //set SPI pins as outputs, unused pins as inputs
    PORTB = 0b11010011; //pull unused pins high
    DDRD = 0x00; //rotary encoder pins as inputs
    PORTD = 0x00; //pull ups off
}

void initExtInt(void)
{
    sei(); //global interrupt enable
    PCICR = 0x04; //enable Port D pin change interrupts
    PCMSK2 = 0xFF; //enable pin change interrupt on all Port D pins
}

void initSPI(void)
{
    SPCR = 0b01010001; //SPI interrupt disabled, SPI enabled, MSB trasmitted first, 
                        //master, rising edge triggered, sample then set up, fosc/8
    SPSR = SPSR | 0x01; //2x clock speed for fosc/8
}

ISR(PCINT2_vect)
{
    unsigned char reg = PCMSK2; //read pin change mask register into intermediate register
    unsigned char send;

    //laser x - left
    if(((reg & 0x80) > 0) && ((PIND & 0x40) > 0)) 
    {
        send = LASER_X_L;
    }

    //laser x - right
    else if(((reg & 0x40) > 0) && ((PIND & 0x80) > 0)) 
    {
        send = LASER_X_R;
    }

    //laser y - down
    else if(((reg & 0x20) > 0) && ((PIND & 0x10) > 0)) 
    {
        send = LASER_Y_D;
    }

    //laser y - up
    else if(((reg & 0x10) > 0) && ((PIND & 0x20) > 0)) 
    {
        send = LASER_Y_U;
    }

    //mirror x - left
    else if(((reg & 0x08) > 0) && ((PIND & 0x04) > 0)) 
    {
        send = MIRROR_X_L;
    }

    //mirror x - right
    else if(((reg & 0x04) > 0) && ((PIND & 0x08) > 0)) 
    {
        send = MIRROR_X_R;
    }

    //mirror y - down
    else if(((reg & 0x02) > 0) && ((PIND & 0x01) > 0)) 
    {
        send = MIRROR_Y_D;
    }

    //mirror y - up
    else if(((reg & 0x01) > 0) && ((PIND & 0x02) > 0)) 
    {
        send = MIRROR_Y_U; 
    }
    else
    {
        send = NOTHING;
    }

    if(send != NOTHING)
    {
        SPDR = send; //start transmission
    }   
}
Tu decodificación de ISR parece bastante sospechosa. ¿Ha comprobado que realmente pone los datos en "enviar"? ¿Está borrando las banderas de interrupción correctamente? ¿Qué pasa si obtienes varias lecturas a la vez? En este momento, solo maneja 1 entrada, después de un esquema de prioridad, ¿es esto intencionado? ¿Cómo sabes que estas entradas están limpias? No haces ningún antirrebote ni filtrado digital. ¿Tiene un filtro de hardware externo? Si es así, por qué, parece innecesariamente caro. En cuanto a SPI, ¿no requiere una verificación/borrado del indicador de estado antes de que se escriban datos en el registro de datos?
Además, nunca use NULL como un valor entero, solo debe usarse para punteros.
Tu decodificación de ISR parece bastante sospechosa. ¿Ha comprobado que realmente pone los datos en "enviar"? Como se indica en la pregunta, ya estuvo allí y se verificó en hardware. ¿Está borrando las banderas de interrupción correctamente? AVR maneja la limpieza de las únicas banderas generadas. ¿Qué pasa si obtienes varias lecturas a la vez? Ese es un estado de indiferencia, porque es muy poco probable que suceda.
En este momento, solo maneja 1 entrada, después de un esquema de prioridad, ¿es esto intencionado? Sí, cada detent en el codificador rotatorio generará 4 interrupciones, hay suficiente tiempo entre ellas para que la transmisión SPI termine antes que la segunda, y el resto no generará casos válidos. ¿Cómo sabes que estas entradas están limpias? Mirándolos en un alcance.
No haces ningún antirrebote ni filtrado digital. ¿Tiene un filtro de hardware externo? El rebote de hardware fue más rápido y más fácil. Si es así, por qué, parece innecesariamente caro. La sobrecarga de software no era deseada y esto no es para producción. En cuanto a SPI, ¿no requiere una verificación/borrado del indicador de estado antes de que se escriban datos en el registro de datos? No tan lejos como he podido encontrar en cualquier lugar, realmente me estoy quedando sin ideas.
Con respecto a la eliminación de rebotes: no generaría mucha sobrecarga, una simple comparación con un valor anterior o un filtro mediano con 3 muestras debería ser suficiente. Con respecto a SPI: no sé cómo se hace en Atmel SPI, pero en Motorola/Freescale SPI que parece muy similar (Motorola inventó el estándar SPI), debe borrar las banderas leyendo un registro de estado. Estas implementaciones de "acceso de lectura borrarán las banderas" son molestas, ya que una depuración puede borrar las banderas deseadas por accidente. Hablaría en RTFM sobre SPI por segunda vez, solo para estar seguro.
¿Por qué se movió esto a un sitio de electrónica? Es una pregunta 100% relacionada con el software y, por lo tanto, 100% sobre el tema del desbordamiento de pila. Si el moderador es un programador de PC sin experiencia en sistemas integrados, entonces por favor no juegue con las preguntas etiquetadas como integradas.
@Lundin Esta pregunta no se migró, ni Matt Young tiene una pregunta similar abierta en SO.

Respuestas (1)

Finalmente encontré la solución a este problema.

Primero, la función de configuración de SPI necesitaba un ajuste.

void initSPI(void)
{
PRR = 0b11111011; //turn power saving on for all peripherals other than SPI
SPCR = 0b11111001; //SPI interrupt enabled, SPI enabled, MSB trasmitted last, 
                    //master, falling edge triggered, set up then sample, fosc/8
SPSR = 0x01; //2x clock speed for fosc/8
}

Además, el procesador no maneja el pin !SS en modo maestro. Debe configurarse en un nivel bajo al comienzo de la transmisión y volverse en un nivel alto después de forma manual.

if(send != NOTHING)
{
    PORTB =  PORTB & 0b11111011;
    SPDR = send; //start transmission
    while(!(SPSR & (1<<SPIF))); //wait for transmission to finish
    PORTB =  PORTB & 0b11111111;
}
Creo que ningún microcontrolador manejará el pin SS/CS porque puede compartir el mismo bus con varios dispositivos y usa SS/CS para seleccionar con qué dispositivo se está comunicando.