View previous topic :: View next topic |
Author |
Message |
soonc
Joined: 03 Dec 2013 Posts: 215
|
I2C Driver for SSD1306 Graphics Chip [Solved] |
Posted: Mon Sep 01, 2014 10:58 pm |
|
|
I'm trying to develop a I2C driver for the SSD1306 OLED graphics controller chip. If I ever get it working I'll be happy to post it .
Progress so far:
Hardware is from Adafruit it's the 128x64 OLED . Configured for I2C.
They have code examples and I'm trying to port to CCS PIC which Adafruit claims should be easy.
Lots of the code is straight forward and the initialization comes from the SSD1306 data sheet.
However they (AdaFruit) and others are using I2C address 0X3C and the data sheet clearly states 0X78 ! 0X3C shifted left become 0x78! Perhaps someone read it wrong but the mistake appears to be copied a lot, if it's not a mistake it may have something to do with Arduino platform.
I decided to use the PIC18LF26K22 at 48MHz. It has enough RAM and ROM for my application.
What my code does not seem to be able to do it transfer the PIC buffer to the SSD1306 RAM.
Logic analyzer tells me IC2 is working.
Display does not light up I guess I forgot something !
Any suggestions please.
The code so far:
// Using PCWHD 4.134
Code: |
#include <18LF26K22.h>
#device ADC=10
#FUSES PRIMARY //Primary clock is system clock when scs=00
#FUSES HSM
#FUSES PLLEN
#FUSES FCMEN //Fail-safe clock monitor enabled
#FUSES IESO //Internal External Switch Over mode enabled
#FUSES NOPUT //No Power Up Timer
#FUSES NOBROWNOUT //No brownout reset
#FUSES BORV19 //Brownout reset at 1.9V
#FUSES MCLR //Master Clear pin enabled
#FUSES STVREN //Stack full/underflow will cause reset
#FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOXINST //Extended set extension and Indexed Addressing mode disabled (Legacy mode)
#FUSES NOPROTECT //Code not protected from reading
#FUSES NOCPB //No Boot Block code protection
#FUSES NOCPD //No EE protection
#FUSES NOWRT //Program memory not write protected
#FUSES NOWRTC //Configuration registers not write protected
#FUSES NOWRTB //Boot block not write protected
#FUSES NOWRTD //Data EEPROM not write protected
#FUSES NOEBTR //Memory not protected from table reads
#FUSES NOEBTRB //Boot block not protected from table reads
#use delay(clock=48000000,crystal=12MHz)
#use FIXED_IO( B_outputs=PIN_B5,PIN_B2 )
#use FIXED_IO( C_outputs=PIN_C5,PIN_C1,PIN_C0 )
#define DISPLAY_RESET PIN_C5
#use i2c(Master,Fast,sda=PIN_C4,scl=PIN_C3,force_hw)
#define SSD1306_LCDWIDTH 128
#define SSD1306_LCDHEIGHT 64
#define SSD1306_SETCONTRAST 0x81
#define SSD1306_DISPLAYALLON_RESUME 0xA4
#define SSD1306_DISPLAYALLON 0xA5
#define SSD1306_NORMALDISPLAY 0xA6
#define SSD1306_INVERTDISPLAY 0xA7
#define SSD1306_DISPLAYOFF 0xAE
#define SSD1306_DISPLAYON 0xAF
#define SSD1306_SETDISPLAYOFFSET 0xD3
#define SSD1306_SETCOMPINS 0xDA
#define SSD1306_SETVCOMDETECT 0xDB
#define SSD1306_SETDISPLAYCLOCKDIV 0xD5
#define SSD1306_SETPRECHARGE 0xD9
#define SSD1306_SETMULTIPLEX 0xA8
#define SSD1306_SETLOWCOLUMN 0x00
#define SSD1306_SETHIGHCOLUMN 0x10
#define SSD1306_SETSTARTLINE 0x40
#define SSD1306_MEMORYMODE 0x20
#define SSD1306_COLUMNADDR 0x21
#define SSD1306_PAGEADDR 0x22
#define SSD1306_COMSCANINC 0xC0
#define SSD1306_COMSCANDEC 0xC8
#define SSD1306_SEGREMAP 0xA0
#define SSD1306_CHARGEPUMP 0x8D
#define SSD1306_EXTERNALVCC 0x1
#define SSD1306_SWITCHCAPVCC 0x2
int8 _i2c_address;
int8 display_buffer[1024];
///////////////////////////////////////
//
void ssd1306_command(int8 c)
{
int8 control = 0x00; // some use 0X00 other examples use 0X80. I tried both
i2c_start();
i2c_write(_i2c_address);
i2c_write(control); // This is Command
i2c_write(c);
i2c_stop();
}
////////////////////////////////////////////
//
void ssd1306_data(int8 c)
{
i2c_start();
i2c_write(_i2c_address);
i2c_write(0X40); // This byte is DATA
i2c_write(c);
i2c_stop();
}
///////////////////////////////////////////////////
// Used when doing Horizontal or Vertical Addressing
void setColAddress()
{
ssd1306_command(SSD1306_COLUMNADDR); // 0x21 COMMAND
ssd1306_command(0); // Column start address
ssd1306_command(SSD1306_LCDWIDTH-1); // Column end address
}
/////////////////////////////////////////////////////
// Used when doing Horizontal or Vertical Addressing
void setPageAddress()
{
ssd1306_command(SSD1306_PAGEADDR); // 0x22 COMMAND
ssd1306_command(0); // Start Page address
ssd1306_command((SSD1306_LCDHEIGHT/8)-1);// End Page address
}
///////////////////////////////////////////////////////////
// Transfers the local buffer to the CGRAM in the SSD1306
void TransferBuffer()
{
int16 j=0;
// set the Column and Page addresses to 0,0
setColAddress();
setPageAddress();
i2c_start();
i2c_write(_i2c_address);
i2c_write(0X40); // data not command
for(j=0;j<1024;j++)
{
i2c_write(display_buffer[j]);
}
i2c_stop();
}
///////////////////////////////////////////////////////////////////
// init according to SSD1306 data sheet and many places on the web
void InitializeDisplay()
{
// reset
output_high(DISPLAY_RESET);
delay_ms(1);
output_low(DISPLAY_RESET);
delay_ms(10);
output_high(DISPLAY_RESET); // Reset Pin High for normal operation
// Init sequence for 128x64 OLED module
ssd1306_command(SSD1306_DISPLAYOFF); // 0xAE
ssd1306_command(SSD1306_SETDISPLAYCLOCKDIV); // 0xD5
ssd1306_command(0x80); // the suggested ratio 0x80
ssd1306_command(SSD1306_SETMULTIPLEX); // 0xA8
ssd1306_command(0x3F);
ssd1306_command(SSD1306_SETDISPLAYOFFSET); // 0xD3
ssd1306_command(0x0); // no offset
ssd1306_command(SSD1306_SETSTARTLINE);// | 0x0); // line #0
ssd1306_command(SSD1306_CHARGEPUMP); // 0x8D
ssd1306_command(0x14); // using internal VCC
ssd1306_command(SSD1306_MEMORYMODE); // 0x20
ssd1306_command(0x00); // 0x00 horizontal addressing
ssd1306_command(SSD1306_SEGREMAP | 0x1); // rotate screen 180
ssd1306_command(SSD1306_COMSCANDEC); // rotate screen 180
ssd1306_command(SSD1306_SETCOMPINS); // 0xDA
ssd1306_command(0x12);
ssd1306_command(SSD1306_SETCONTRAST); // 0x81
ssd1306_command(0xCF);
ssd1306_command(SSD1306_SETPRECHARGE); // 0xd9
ssd1306_command(0xF1);
ssd1306_command(SSD1306_SETVCOMDETECT); // 0xDB
ssd1306_command(0x40);
ssd1306_command(SSD1306_DISPLAYALLON_RESUME); // 0xA4
ssd1306_command(SSD1306_NORMALDISPLAY); // 0xA6
ssd1306_command(SSD1306_DISPLAYON); //switch on OLED
}
//////////////////////////////
//
void main()
{
output_high(PIN_C1);// keep power ON
output_float(PIN_C3);
output_float(PIN_C4);
// fill buffer with something for test
memset( display_buffer, 0X02, 1024); // tried other values
_i2c_address = 0X78; // this works 0X3C or 0X3D does not
InitializeDisplay();
TransferBuffer(); // try sending buffer
while(1)
{
; // keyboard code here
}
}
|
|
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19539
|
|
Posted: Tue Sep 02, 2014 12:36 am |
|
|
You need to read up about I2C. It's an I2C thing about how addresses are actually handled.
On I2C, the first byte sent, is the 7bit address, plus the single bit 'flag' for the transaction direction.
So a device with an I2C address of 0x3C, requires bytes of 0x78 or 0x79, for write/read respectively. The original I2C documentation uses the 7bit address, while a lot of modern documentation instead uses the 8bit byte value.
Now on the Arduino code, they keep the address, and the direction flag separate, so you call the function with an address of 0x3C, and the direction flag. On the CCS functions, you send the full 8bit value as one item. |
|
|
soonc
Joined: 03 Dec 2013 Posts: 215
|
|
Posted: Tue Sep 02, 2014 6:43 am |
|
|
Ttelmah wrote: | ....
Now on the Arduino code, they keep the address, and the direction flag separate, so you call the function with an address of 0x3C, and the direction flag. On the CCS functions, you send the full 8bit value as one item. |
Thanks for the clarification. I'm not using Arduino.
I'm doing my own hardware and using the Adafruit OLED module as it's a convenient format and saves me the extra effort of making it myself.
In I2C mode this device cannot be read so I'm using 0X78 .
Now to the results so far:
The way I read it after calling the InitializeDisplay() I expected the display should be lighting up random pixels as the GCRAM has not been cleared.
Most code examples I've looked at clear the buffer or transfer a graphic right after the InitializeDisplay().
I think I most have missed something.
The chip is initialized.
The addressing mode is set to horizontal.
The column and page addresses are set to with start and end values.
Vdd is 3.3V, and I2C has 3K3 pull up resistors. I2C is outputting the values according to the logic analyzer. I tried another display but as always is the case it's never the chip ! |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Tue Sep 02, 2014 10:15 am |
|
|
Quote: | They have code examples and I'm trying to port to CCS PIC which
Adafruit claims should be easy. |
Post a link to this driver so we can compare it to your code. |
|
|
soonc
Joined: 03 Dec 2013 Posts: 215
|
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Tue Sep 02, 2014 2:23 pm |
|
|
Quote: | Logic analyzer tells me IC2 is working. |
This means you see activity on SCL and SDA, but it doesn't mean you
are getting a response from the OLED chip.
Did you run the i2c bus scanner program to see if it says the OLED
slave address is 0x7c:
http://www.ccsinfo.com/forum/viewtopic.php?t=49713 |
|
|
soonc
Joined: 03 Dec 2013 Posts: 215
|
I2C address confirmed |
Posted: Tue Sep 02, 2014 4:24 pm |
|
|
PCM programmer wrote: | Quote: | Logic analyzer tells me IC2 is working. |
This means you see activity on SCL and SDA, but it doesn't mean you
are getting a response from the OLED chip.
Did you run the i2c bus scanner program to see if it says the OLED
slave address is 0x7c:
http://www.ccsinfo.com/forum/viewtopic.php?t=49713 |
OK I tried your suggestion in my code here is the test routine which is essentially the same as your test routine except I don't have a display to do the printf() so I used the display_buffer[] and dump the results in there.
Thanks for the idea it confirms my use of 0X7A as the correct address.
I'll fine comb my code I feel sure I forgot something.
Code: |
// in main()
int8 ack_status=0;
int8 i;
for( j=0; j<1024; j++)
{
display_buffer[j]=0; // clean array
}
for( i=0x10; i<0XF0; i+=2)
{
i2c_start();
ack_status = i2c_write(i);
i2c_stop();
if( !ack_status )
{
display_buffer[i] = i;
}
}
i=0; // break point.
// Using PCHWD look for a non zero in the array
// location 88 and 122 are non-zero. 88 is a digital pot, and 122=0X7A
// which is the OLED display.
|
|
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Tue Sep 02, 2014 4:35 pm |
|
|
Quote: | Thanks for the idea it confirms my use of 0X7A as the correct address. |
Were you using 0x7A ?
In your original progam you had this:
Quote: | _i2c_address = 0X78; // this works 0X3C or 0X3D does not |
So if you put 0x7A in the above line, does your OLED driver now work ? |
|
|
soonc
Joined: 03 Dec 2013 Posts: 215
|
Problem Solved |
Posted: Tue Sep 02, 2014 7:32 pm |
|
|
PCM programmer wrote: | Quote: | Thanks for the idea it confirms my use of 0X7A as the correct address. |
Were you using 0x7A ?
In your original progam you had this:
Quote: | _i2c_address = 0X78; // this works 0X3C or 0X3D does not |
So if you put 0x7A in the above line, does your OLED driver now work ? |
That's it.... The I2C Address is 0X7A and I had it set at 0X78
0b01111010 = 0X7A the SA0 line is set high in the OLED module.
I just tested the original code with the correct address and it works.
How I miss these small details ! This morning I found out I have cataracts in both eyes ! I'm removing the cataracts in 14 days, but I doubt I can blame it on that.
Anyway your modified test routine found the problem.
Thanks. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19539
|
|
Posted: Wed Sep 03, 2014 12:42 am |
|
|
PCM_Programmers I2C test routine is one of those 'core' bits of code here, that all the 'old hands' will use, when trying to get an I2C project working. It is simple, and proves that you are successfully talking to the chip (so several parts of the wiring are OK), and at what address it sits.
Now you can move forward. |
|
|
superfranz
Joined: 05 Sep 2014 Posts: 10 Location: Trieste, Italy
|
|
Posted: Fri Sep 05, 2014 6:56 am |
|
|
Hi!
I'm working on ssd1306 i2c driver.
I'm using pic 16f1877, and tested on 18f4525
It's working!
pixel(x,y), line, circle, rect, text like glcd lib and large number fullscreen 99.9 style (from ttf font calibri),for my real needs.
...using little more than 1k RAM
If you want i can post my code. It's newbie, no RAM optimized (full of vars ),confused and uncommented (yet) but good-working.
(maybe you can help me in WHY hardware i2c doesn't work!)
Let me know if you're interested and i can post it when back home.
bye bye
Superfranz |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9243 Location: Greensville,Ontario
|
|
Posted: Fri Sep 05, 2014 6:59 am |
|
|
nice to hear it's 'up and running'
yes, you should post your code into the 'code library', the 'other ' forum on this site !!
jay |
|
|
superfranz
Joined: 05 Sep 2014 Posts: 10 Location: Trieste, Italy
|
|
Posted: Sat Sep 06, 2014 5:20 pm |
|
|
done
SuperFranz |
|
|
miskol
Joined: 01 Feb 2011 Posts: 16
|
|
Posted: Mon Sep 22, 2014 8:28 pm |
|
|
interested on this project, i'm currently trying to port/build the SSD1306 driver for use with CCS too but i'm using the SPI OLED.
hope you could share your ported library.
best of all, i'm using the same PIC too!! hehe
please help, TQ |
|
|
akis_t
Joined: 20 Dec 2014 Posts: 1
|
|
Posted: Sat Dec 20, 2014 3:15 am |
|
|
If anyone is still reading this.
I am using the 128 x 64 SSD1306 OLED communicating over I2C.
It is all working fine, except it is slow.
It takes 6.5 ms to simply write a line of text to the display, as each character has to be plotted pixel by pixel. This operation simply manipulates the local memory buffers.
Then it takes another 30ms to send those buffers over to the OLED.
The code uses the Adafruit SSD1306 libraries which work by maintaining a local memory buffer representing the OLED display and sending the whole buffer across every time you need to show something.
Can you please tell me if there is a better way of doing this. |
|
|
|