2
\$\begingroup\$

Scenario

I have an ATMega168 using an external 10MHz crystal. The DIV/8 fuse bit is not set. The processor takes the outputs from 4 quadrature rotary encoders. Their rotation direction information is decoded using pin change interrupts and the pin states. The rotation information is converted to control codes and is send over SPI to another processor handling some motor driving.

Problem

In the code listed, the rotation direction information gets decoded and converted to control codes as designed, and has been verified in hardware. The SPI transmission fails to start. Looking at the SPI pins on the scope, !SS pin stays high, and the SCK and MOSI stay low.

Two other people have looked at this code other than me. I know I have to be overlooking something simple. Why isn't the transmission starting?

/*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
    }   
}
\$\endgroup\$
  • \$\begingroup\$ You decoding ISR looks quite suspect. Have you checked that it actually puts any data into "send"? Are you clearing interrupt flags correctly? What if you get several readings at once? Right now you only handle 1 input, after some priority scheme, is this intended? How do you know these inputs are clean? You don't do any debouncing or digital filtering. Do you have an external hardware filter? If so, why, it seems needlessly expensive. As for SPI, doesn't it require a status flag check/clear before any data is written to the data register? \$\endgroup\$ – Lundin Feb 11 '13 at 10:23
  • \$\begingroup\$ Also, never use NULL as an integer value, it should only be used for pointers. \$\endgroup\$ – Lundin Feb 11 '13 at 10:24
  • \$\begingroup\$ You decoding ISR looks quite suspect. Have you checked that it actually puts any data into "send"? As stated in the question, already been there and verified in hardware. Are you clearing interrupt flags correctly? AVR handles clearing the only flags generated. What if you get several readings at once? That's a don't care state, because it is extremely unlikely to happen. \$\endgroup\$ – Matt Young Feb 13 '13 at 15:51
  • \$\begingroup\$ Right now you only handle 1 input, after some priority scheme, is this intended? Yes, each detent on the rotary encoder will generate 4 interrupts, there is enough time between them that the SPI transmission would finish before the second one, and the rest will not generate valid cases. How do you know these inputs are clean? By looking at them on a scope. \$\endgroup\$ – Matt Young Feb 13 '13 at 15:51
  • \$\begingroup\$ You don't do any debouncing or digital filtering. Do you have an external hardware filter? Hardware debounce was faster and easier. If so, why, it seems needlessly expensive. The software overhead was undesired, and this is not for production. As for SPI, doesn't it require a status flag check/clear before any data is written to the data register? Not as far as I've been able to find anywhere, really running out of ideas. \$\endgroup\$ – Matt Young Feb 13 '13 at 15:52
1
\$\begingroup\$

Finally found the solution to this problem.

First, the SPI set up function needed adjustment.

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
}

Additionally, the processor does not handle the !SS pin in master mode. It needs to be set low at the start of transmission and returned high afterwards manually.

if(send != NOTHING)
{
    PORTB =  PORTB & 0b11111011;
    SPDR = send; //start transmission
    while(!(SPSR & (1<<SPIF))); //wait for transmission to finish
    PORTB =  PORTB & 0b11111111;
}
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I believe no microcontroller will handle the SS/CS pin because you can share the same bus with multiple devices and you use SS/CS to select which device you are communicating with. \$\endgroup\$ – efox29 Feb 17 '13 at 23:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.