Trouble with Hardware SPI clock

Go To Last Post
10 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi,

  I'm trying to use the USART0-SPI mode on my ATmega1284 project. After much fiddling, I have it almost working.

It is connected to an OLED display controlled by an SSD1306 controller. Once the display is initialised, it is switched into data mode, each byte transferred is displayed on screen and the display ram wraps around to the start so my program is set to write out the display buffer every ~33ms. So far so good.

In my old bit-bang spi code, the data pin is set high or low for each bit of the byte to send, then the clock is cycled HIGH > LOW.

My new code sets up the SPI device, then sends each byte:

void main()
{
    UBRR0 = 0;
    UCSR0C = 0b11000000;
    UCSR0B = (0<<RXEN0) | (1<<TXEN0);
    //shift out buffer
    
}
void shift_out_byte(byte b)
{
    
    while( !( UCSR0A & (1<<UDRE0) ) );
    UDR0 = b;
}

The correct number of bytes are being transmitted, but not in quite the right order:

 

 

which leads me to believe that the clock is being pulsed at the wrong time.

This *should* be configured by the bottom two bits of the UCSR0C register, the SSD1306 data sheet says it clocks in on the rising edge, which I believe is 01 for the bottom 2 bits, but changing these bits has no effect. I tried all 4 combinations, one stops the whole thing working (10 - blank screen) and the other 3 all produce the result above.

 

Can anyone help me see what I've done wrong?

 

-Mike
 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The more I think about it, the less I'm convinced the clock is the issue - it's more like a byte is being transmitted BEFORE it goes into the render mode so it's clocking out the display buffer correctly, but it's been offset by a single rogue byte being sent out at the start.

Trouble is I can't find any code that might do this.

Full code is online here https://github.com/MalphasWats/spike-game-template if anyone fancies looking.

What's frustrating is that it successfully sends all the initialisation stuff to the display.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok, so I fixed it, but I have no idea why my fix worked.

 

I have an 'initialise_oled' function that sends all the various commands needed to configure the display. At the end of the function I put the DATA/COMMAND pin HIGH, which is DATA mode and the program just writes the screen buffer in a loop.

 

I have added code that sets CS HIGH for the display just before it swaps to DATA mode, then re-enables the screen just as the draw loop interrupt is enabled.

 

Previously it looked like this:

 

UBRR0 = 0;
UCSR0C = 0b11000011;
UCSR0B = (0<<RXEN0) | (1<<TXEN0);
    
sei();                  // Enable interrupts
    
/* Setup Display */
initialise_oled();
// OLED IN DATA MODE HERE, ENABLED

front_buffer = &buffer1[0];
buffer = &buffer2[0];

TIMSK2 = 0x02;          // Enable OCR2A Interrupt

with the end of initialise_oled doing this:

 

initialise_oled() {
    ...
    shift_out_byte(0xAF);               // DISPLAYON
        
    shift_out_byte(0xB0 + 0);           // PAGEADDR (0 = reset)
    shift_out_byte(0 & 0x0F);           // Column start address (0 = reset)
    shift_out_byte(0x10 | (0 >> 4));    // LOW COL ADDR
        
    PORTB |= 1 << DC;                   // DATA
}

now it looks like this:

 

...
initialise_oled();
// OLED IN DATA MODE HERE, DISABLED

front_buffer = &buffer1[0];
buffer = &buffer2[0];

TIMSK2 = 0x02;          // Enable OCR2A Interrupt
PORTB &= ~(1 << CS);    // LOW (Enabled)

and

 

initialise_oled() {
    ...
    
    PORTB |= 1 << CS;                   // HIGH (Disabled)    
    PORTB |= 1 << DC;                   // DATA
}

and it works, yippee! I have absolutely no idea why though.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

No,  I dont't fancy trawling through the internet.

 

But YOU can replace magic values with the  appropriate BIT_VALUES.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:

No,  I dont't fancy trawling through the internet.

 

But YOU can replace magic values with the  appropriate BIT_VALUES.

 

They were originally BIT_VALUES, but the io.h file didn't have the same names as are in the data sheet and I wanted to be certain I was actually setting the correct bits. There was no BIT_VALUE for UCPHA0 so I went back to the register description table and set the bits myself to make sure.

 

Thanks

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

MalphasWats wrote:
I have added code that sets CS HIGH for the display just before it swaps to DATA mode

CS is important to sync the start of data in SPI, seems to be an overlooked function as it is usually not mentioned in the SPI doc.

 

Jim

 

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274
get $5 free gold/silver https://www.onegold.com/join/713...

 

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ki0bk wrote:

portant to sync the start of data in SPI, seems to be an overlooked function as it is usually not mentioned in the SPI doc.

 

 

Indeed - In my previous version of this project I was pin constrained and had it tied straight to ground!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

MalphasWats wrote:
There was no BIT_VALUE for UCPHA0 so I went back to the register description table and set the bits myself to make sure.
It is a shame the header file only has:

#define UCPOL0  0
#define UCSZ00  1
#define UCSZ01  2
#define USBS0   3
#define UPM00   4
#define UPM01   5
#define UMSEL00 6
#define UMSEL01 7

I'd be tempted to put this in my own code:

#include <avr/io.h>

#define UCPOL 0
#define UCPHA 1
#define UDORD 2

to make up for the deficiencies of the header then later:

UCSR0C = (3 << UMSEL0) | (0 << UDORD) | (1 << UCPHA) | (0 << UCPOL);

(or whatever the chosen setting is)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

I'd be tempted to put this in my own code:

#include <avr/io.h>

#define UCPOL 0
#define UCPHA 1
#define UDORD 2

Sometimes you gotta do what you gotta do, but this may be a bit better:

 

#ifndef UCPOL
#define UCPOL 0
#endif

// etc.....

--Mike

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank you. I've 'refactored' it a bit and used the provided BIT_VALUES but added a comment explaining their equivalence. I would likely have forgotten what they were for if I'd defined them myself so probably would have added comments there anyway. This way it's all kept together where it's relevant.

Thank you all for your feedback.