ATMEGA328 Pantalla I2C / TWI y OLED

0

Tengo ATMEGA328P y pantalla I2C (SSD1306). Estoy intentando simplemente colocar un solo píxel en una pantalla con el menor código posible para poder aprender desde allí.

Pude hacer esto con frambuesa usando el siguiente código:

// gcc ssd1306.c -lwiringPi -o ssd1306

#include <stdio.h>
#include <string.h>
#include <wiringPiI2C.h>

#define WIDTH 128
#define HEIGHT 64

int buffer[ WIDTH * HEIGHT / 8 ];
int i2cd;

void ssd1306_command(unsigned int c)
{
    unsigned int control = 0x00;
    wiringPiI2CWriteReg8( i2cd, control, c );
}

void ssd1306_byte(unsigned int c)
{
    unsigned int control = 0x40;
    wiringPiI2CWriteReg8( i2cd, control, c );
}

void drawPixel( int x, int y, unsigned int color )
{
    switch (color) 
    {
        case 1: // white
            buffer[x + ( y / 8 ) * WIDTH ] = 1;

            break;
        case 0: // black
            buffer[x + ( y / 8 ) * WIDTH ] = 0;
            break;
    }
}

void init()
{
    i2cd = wiringPiI2CSetup( 0x3C ); // address

    ssd1306_command(0xAE);          // 0xAE // display off
    ssd1306_command(0xD5);          // 0xD5 // set display clock division
    ssd1306_command(0x80);          // the suggested ratio 0x80
    ssd1306_command(0xA8);          // 0xA8 set multiplex
    ssd1306_command(63);            // set height
    ssd1306_command(0xD3);          // set display offset
    ssd1306_command(0x0);           // no offset
    ssd1306_command(64);            // line #0 setstartline
    ssd1306_command(0x8D);          // 0x8D // chargepump
    ssd1306_command(0x14);
    ssd1306_command(0x20);          // memory mode
    ssd1306_command(0x00);          // 0x0 act like ks0108
    ssd1306_command(161);           // segremap
    ssd1306_command(0xC8);          // comscandec
    ssd1306_command(0xDA);          // 0xDA set com pins
    ssd1306_command(0x12);
    ssd1306_command(0x81);          // 0x81 // set contract
    ssd1306_command(0xCF);
    ssd1306_command(0xD9);          // 0xd9 set pre-charge
    ssd1306_command(0xF1);
    ssd1306_command(0xDB);          // SSD1306_SETVCOMDETECT
    ssd1306_command(0x40);
    ssd1306_command(0xA4);          // 0xA4 // display all on resume
    ssd1306_command(0xA6);          // 0xA6 // normal display
    ssd1306_command(0x2E);          // deactivate scroll
    ssd1306_command(0xAF);          // --turn on oled panel
}

void renderBuffer(void)
{
    ssd1306_command(0x21);          // column address
    ssd1306_command(0);             // Column start address (0 = reset)
    ssd1306_command(127);           // Column end address (127 
    ssd1306_command(0x22);          // page address
    ssd1306_command(0x00);          // Page start address (0 = reset)
    ssd1306_command(7);             // Page end address

    int i;

    for (i = 0; i < ( 128 * 64 / 8 ); i++) 
    {
        ssd1306_byte( buffer[i] ); 
    }
}

void clearBuffer(void)
{
    memset( buffer, 0, ( 128 * 64 / 8 ) * sizeof( int ) );
}

void main() 
{
    init();
    clearBuffer();
    drawPixel( 10, 10, 1 );
    renderBuffer();
}

Todo funciona perfectamente hasta ahora cuando trato de hacer lo mismo con i2c_master.c para mi ATMEGA: enlace

#ifndef F_CPU
    #define F_CPU 8000000UL
#endif

#include <avr/io.h>
#include <util/delay.h>
#include <stdlib.h>
#include "i2c_master.c"
#include "i2c_master.h"

#define  SSD1306_ADDRESS 0x3C

void initDisplay()
{
    i2c_start(SSD1306_ADDRESS);

    i2c_write(0xAE);          // 0xAE // display off
    i2c_write(0xD5);          // 0xD5 // set display clock division
    i2c_write(0x80);          // the suggested ratio 0x80
    i2c_write(0xA8);          // 0xA8 set multiplex
    i2c_write(63);            // set height
    i2c_write(0xD3);          // set display offset
    i2c_write(0x0);           // no offset
    i2c_write(64);            // line #0 setstartline
    i2c_write(0x8D);          // 0x8D // chargepump
    i2c_write(0x14);
    i2c_write(0x20);          // memory mode
    i2c_write(0x00);          // 0x0 act like ks0108
    i2c_write(161);           // segremap
    i2c_write(0xC8);          // comscandec
    i2c_write(0xDA);          // 0xDA set com pins
    i2c_write(0x12);
    i2c_write(0x81);          // 0x81 // set contract
    i2c_write(0xCF);
    i2c_write(0xD9);          // 0xd9 set pre-charge
    i2c_write(0xF1);
    i2c_write(0xDB);          // SSD1306_SETVCOMDETECT
    i2c_write(0x40);
    i2c_write(0xA4);          // 0xA4 // display all on resume
    i2c_write(0xA6);          // 0xA6 // normal display
    i2c_write(0x2E);          // deactivate scroll
    i2c_write(0xAF);          // --turn on oled panel

    i2c_stop();
}

void drawPixel()
{
    i2c_start( SSD1306_ADDRESS );

    i2c_write(0x21);          // column address
    i2c_write(0);             // Column start address (0 = reset)
    i2c_write(127);           // Column end address (127 
    i2c_write(0x22);          // page address
    i2c_write(0x00);          // Page start address (0 = reset)
    i2c_write(7);             // Page end address

    int i;

    int z=0;

    for ( i = 0; i < ( 128 * 64 / 8 ); i++ ) 
    {
        if ( z == 0 )
        {
            i2c_write( 0xff ); 
            z = 1;
        }
        else
        {
            i2c_write( 0x00 ); 
            z = 0;
        }
    }

    i2c_stop();
}

int main(void){

    i2c_init();
    initDisplay();
    drawPixel();

    return 0;
}

pero el píxel no está dibujado ..

Uso los siguientes ajustes de fusibles (uso 8Mhz interno) lfuse: w: 0xe2: m -U hfuse: w: 0xd9: m

Tengo la siguiente configuración de hardware:

-ATMEGA328P connected to a 4.0v power source
-ADC5 (SCL) connected to OLED's SCL (with additional line to VCC with 15k resistor in between) 
-ADC4 (SDA) connected to OLED's SDA (with additional line to VCC with 15k resistor in between)

También intenté eliminar las resistencias y 'la conexión vcc extra', y lo conecté de la misma forma que lo hice con la frambuesa, pero no hice ninguna diferencia. ¿Alguna pista sobre qué estoy haciendo mal? He estado atrapado en esto por días ... ¡Gracias!

Información adicional:

  

(a) Por favor, proporcione una foto del h / w.

  

(b)SuministrelahojadedatosparaelmóduloOLED.

enlace

  

(c) ¿Ha medido si el módulo OLED tiene pull-ups I2C?   incorporados y habilitados, o son sus 15k pull-ups los únicos en el   autobús?

Creo que no hay pull-ups incorporados. Aquí hay una cita de la hoja de datos:

Both the data line (SDA) and the clock line (SCL) should be pulled up by external resistors
  

(d) ¿Tiene acceso a un osciloscopio y tiene experiencia en su uso?

Lamentablemente no tengo un osciloscopio ...

  

(e) Si es así, ¿puede proporcionar rastros que muestren el aumento & caída de un pequeño   ¿Muestra de ambas señales I2C?

-

  

(f) Aunque es menos útil que un alcance en esta etapa, ¿tiene   acceso a un analizador lógico, incluso a uno barato?

Desafortunadamente, tampoco tengo un analizador lógico ...

    
pregunta 0x29a

3 respuestas

1

El primer byte transmitido en una transacción I2C es la dirección del esclavo (7 bits) más el único bit de lectura / escritura. Entonces, si la dirección del esclavo es 0x3c, lo siguiente se transmite como primer byte:

0x78  // for starting a write transaction
0x79  // for starting a read transaction

Eso es 0x3c cambiado en 1 bit más, para transacciones de lectura, el conjunto LSB.

La librería Raspberry parece manejar esto automáticamente. La biblioteca ATMEGA328P parece requerir que lo hagas manualmente.

Así que usa 0x78 como la dirección y vuelve a intentarlo.

Actualizar

Hay problemas adicionales en su código: debe indicar al controlador de pantalla si envía comandos (y parámetros de comando) o datos. Básicamente, tiene que prefijar 0x80 antes de cada byte de comando y 0x40 antes de enviar datos. Una vez que use 0x40, todos los bytes hasta la condición de detención se tratarán como bytes de datos. Consulte la figura 8-7 y el capítulo 8.1.5.2 iluminado. 5 en la hoja de datos.

Es por eso que hay ssd1306_command y ssd1306_byte en el código de Raspberry. (Obviamente, 0x00 también funciona en lugar de 0x80). Por lo tanto, debe agregar 0x80 antes de cada byte de inicialización, así como antes de los comandos de dirección cuando se procesa un búfer. Y debe agregar 0x40 antes de enviar el primer byte de datos.

Tenga en cuenta que también tiene una diferencia con las transacciones. (Una transacción comienza con la condición START y termina con la condición STOP). En el código de Raspberry, cada byte de comando y cada byte de datos se envían en una transacción separada. En el código ATMEGA328P, lo combina en una transacción mucho menor pero más grande. Ambos enfoques funcionan. El último es más eficiente. Esto podría ser relevante para algunas diferencias menores, como 0x00 vs 0x80.

Solo como referencia: puede encontrar mi código de referencia para la pantalla OLED en Swift y C # . Es un código de prueba para Wirekite : una solución de código abierto para conectar I / Os, incluido I2C a tu Mac o PC, con un precio económico. Tablero Teensy y un cable USB. El código específico de la aplicación se ejecuta en su Mac o PC; el código en el Teensy es fijo.

    
respondido por el Codo
1

No puedo estar exactamente seguro y, como todavía no tengo el privilegio de comentar,

¿No cree que debe iniciar o reiniciar la comunicación TWI después de cada comando o enviar una parada y comenzar de nuevo?

Si esa es su propia biblioteca, puede leer el código de estado de TWI o I2C del registro de TWSR y eso le ayudaría a solucionar este problema. Realmente podría verificar qué parte de sus instrucciones falla al comparar con el código de estado que se encuentra en la Hoja de datos.

EDIT: Ya no hay conjeturas, la primera parte de la respuesta es incorrecta. No, no necesitas comenzar ni parar un poco después de leer y escribir. Entonces, para enviar un comando a su Chip, necesita enviar datos de 16 bits, de los cuales los primeros 8 bits de MSB determinan si está enviando los datos o el comando. ¿Estás haciendo eso?

    
respondido por el MaNyYaCk
-1

La forma más sencilla de exportar es escribir su propia versión de wiringPiI2CWriteReg8 y la función de configuración correspondiente.

Vuelva a compilar y ya debería estar listo para comenzar.

    
respondido por el dannyf

Lea otras preguntas en las etiquetas