1
\$\begingroup\$

I have ATmega328 directly connected to the SSD1306 Display through I2C. Whenever I power up the ATmega, everything works great and the display works as expected.

The problem occurs only when I upload a new program to the ATmega while the ATmega is already running. Then the display goes out of sync ether horizontally or vertically and starts at a random offset.

This is the expected result, I can power it up 50x in a row and it still is in sync (rendering starts from 1st pixel of x coordinate):

enter image description here

This is how it acts when I re-program it, it goes out-of-sync ether vertically or horizontally (rendering starts from random location) (if I power it off-on it will work fine again):

enter image description here

I'm not sure how to solve this or if this is a software or hardware issue. Any hints would be much appreciated. I will post the code just in case:

twi.c

void TWIInit()
{
    TWSR = 0x00;          // prescaler
    TWBR = 0x0C;          // divider
    TWCR = ( 1 << TWEN ); // enable TWI
}


void TWIStart( void )
{
    TWCR = ( 1 << TWINT ) | ( 1 << TWSTA ) | ( 1 << TWEN ) | ( 1 << TWEA );

    while ( ( TWCR & ( 1 << TWINT ) ) == 0 );
}


void TWIStop( void )
{
    TWCR = ( 1 << TWINT ) | ( 1 << TWSTO ) | ( 1 << TWEN );
}


void TWIWrite( uint8_t u8data )
{
    TWDR = u8data;
    TWCR = ( 1 << TWINT ) | ( 1 << TWEN );

    while ( ( TWCR & ( 1 << TWINT ) )  == 0 );
}


uint8_t TWIReadACK()
{
    TWCR = ( 1 << TWINT ) | ( 1 << TWEN ) | ( 1 << TWEA );

    while ( ( TWCR & ( 1 << TWINT ) ) == 0 );
    
    return TWDR;
}


uint8_t TWIReadNACK()
{
    TWCR = ( 1 << TWINT ) | ( 1 << TWEN );

    while ( ( TWCR & ( 1 << TWINT ) ) == 0 );

    return TWDR;
}


uint8_t TWIGetStatus()
{
    uint8_t status;
    status = TWSR & 0xF8;
    
    return status;
}

ssd1306.c

static int     BUFFER_SIZE   = 128 * 64 / 8;
static uint8_t SCREEN_WIDTH  = 128;

uint8_t screen_buffer[ 128 * 64 / 8 ];


void ssd1306_init( void )
{
    TWIStart();

    TWIWrite( 0x3c << 1 );     // addr?
    TWIWrite( 0xAE );          // display off
    TWIWrite( 0xD9 );          // set display clock division
    TWIWrite( 0x80 );          // the suggested ratio 0x80
    TWIWrite( 0xA8 );          // set multiplex
    TWIWrite( 63   );          // set height
    TWIWrite( 0xD3 );          // set display offset
    TWIWrite( 0x0  );          // no offset
    TWIWrite( 64   );          // line #0 setstartline
    TWIWrite( 0x8D );          // chargepump
    TWIWrite( 0x14 );
    TWIWrite( 0x20 );          // memory d
    TWIWrite( 0x00 );          // 0x0 act like ks0108
    TWIWrite( 161  );          // segremap
    TWIWrite( 0xC8 );          // comscandec
    TWIWrite( 0xDA );          // set com pins
    TWIWrite( 0x12 );
    TWIWrite( 0x81 );          // set contrast
    TWIWrite( 0xFF );
    TWIWrite( 0xD9 );          // 0xd9 set pre-charge
    TWIWrite( 0xF1 );
    TWIWrite( 0xA7 );
    TWIWrite( 0xDB );          
    TWIWrite( 0x40 );
    TWIWrite( 0xA4 );          // display all on resume
    TWIWrite( 0xA7 );          // normal display
    TWIWrite( 0x2E );          // deactivate scroll
    TWIWrite( 0xAF );          // turn on oled panel

    TWIStop();
}


void ssd1306_on()
{
    TWIStart();

    TWIWrite( 0x3c << 1 );
    TWIWrite( 0xA4 );          // display all on resume
    TWIWrite( 0xA6 );          // normal display
    TWIWrite( 0x2E );          // scroll
    TWIWrite( 0xAF );          // turn on oled panel

    TWIStop();
}


void ssd1306_off()
{
    TWIStart();

    TWIWrite( 0x3c << 1 );
    TWIWrite( 0xA4 );          // display all on resume
    TWIWrite( 0xA6 );          // normal display
    TWIWrite( 0x2E );          // deactivate scroll
    TWIWrite( 0xAE );          // display off

    TWIStop();
}


void ssd1306_brightness( int brightness )
{
    TWIStart();

    TWIWrite( 0x3c << 1 );
    TWIWrite( 0x20 );          // memory mode
    TWIWrite( 0x81 );          // contrast
    TWIWrite( brightness );

    TWIStop();
}


void ssd1306_render()
{
    TWIStart();
    TWIWrite( 0x3c << 1 );
    TWIWrite( 0x40 );

    for( int i=0; i<BUFFER_SIZE; i++ )
        TWIWrite( screen_buffer[ i ] );

    TWIStop();
}


void ssd1306_clear()
{
    memset( screen_buffer, 0x00, BUFFER_SIZE );
}


void ssd1306_invert()
{
    TWIStart();

    TWIWrite( 0x3c << 1 );
    TWIWrite( 0xA7 );

    TWIStop();
}


void ssd1306_drawPixel( int x, int y )
{
   screen_buffer[ x + ( y / 8 ) * SCREEN_WIDTH ] |= _BV( ( y % 8 ) ); 
}


void ssd1306_clearPixel( int x, int y )
{
   screen_buffer[ x + ( y / 8 ) * SCREEN_WIDTH ] &= ~_BV( ( y % 8 ) ); 
}

ssd1306_test.c

#define F_CPU 8000000L

#include <avr/io.h>
#include <util/delay.h>
#include <math.h>

#include "protocols/twi.c"
#include "drivers/ssd1306.c"

int main( void )
{
    ssd1306_init();
    ssd1306_on();
 
    int x = 1;

    while( 1 )
    {
        int y1 = sin( x / 2.0 ) * 5;
        int y2 = cos( x / 4.0 ) * 5;

        ssd1306_drawPixel( x, y1 + 15 );
        ssd1306_drawPixel( x, y2 + 40 );
        ssd1306_render();

        x += 1;

        if( x > 127 )
        {
            x = 0;
            ssd1306_clear();
        }

        _delay_ms( 10 );        
    }

}

To build it I use the following commands:

/usr/bin/avr-gcc -g -Os -Wall -mcall-prologues -mmcu=atmega328p   -c -o ssd1306_test.o ssd1306_test.c
/usr/bin/avr-gcc -g -Os -Wall -mcall-prologues -mmcu=atmega328p ssd1306_test.o -o ssd1306_test.obj
/usr/bin/avr-objcopy -R .eeprom -O ihex ssd1306_test.obj ssd1306_test.hex
/usr/bin/avrdude -c linuxgpio -p m328p -e -B 1.0 -U lfuse:w:0xe2:m -U hfuse:w:0xd9:m
\$\endgroup\$
5
  • 3
    \$\begingroup\$ Does the problem happen if you only reset the Atmega, instead of reprogramming it? Can you reset or control the display power in software? Can you do software reset via I2C? Do you rely on some register value being at certain state after reset? Do the first I2C transtactions even work if a transaction was interrupted in the middle? \$\endgroup\$
    – Justme
    Commented May 26 at 15:26
  • \$\begingroup\$ If I reset Atmega through reset pin, the same problem occurs. So it only works as expected, if I "power it up". So that means my init sequence is not valid in the first place, and due to some timing it just happens to work, correct? I can turn on/off screen through software, but it didn't make a change, I added delays before initing 1306, but still the same result. I will double check the init sequence, maybe I'm doing something incorrectly as well as if there is a way to reset it through the software. There is no hardware reset on ssd1306. \$\endgroup\$
    – 0x29a
    Commented May 27 at 8:09
  • \$\begingroup\$ I do not rely on the registers to be in certain state, but the ssd1306 might internally, so I will try to find some reset sequence which might help. About the first I2C transaction I'm not sure, I don't have oscilloscope, but it appears that the transactions work after re-programming since it's taking in the consideration the arguments i passed in the init sequence, it just appears there's some kind of timing issue \$\endgroup\$
    – 0x29a
    Commented May 27 at 8:12
  • \$\begingroup\$ Please add new information like these clarifications to your question by editing it. ;-) This site is not a forum, and comments are not expected to carry relevant information. \$\endgroup\$ Commented May 27 at 8:43
  • \$\begingroup\$ @0x29a But the display remembers the parameters sent to it because it is not reset or unpowered. So it is possible that your init is not sent. You also don't check if the TWI operations fail or succeed so you don't know what happens. \$\endgroup\$
    – Justme
    Commented May 27 at 9:50

0

Browse other questions tagged or ask your own question.