microcontroller doesn't work with Nokia 5110 LCD (PCD8544)

I'm struggling with my first AVR microcontroller - AtTiny85 to make it work with the known Nokia 5110 display. I wrote my own code to handle the LCD, it didn't work. Then I tried a code found somewhere on the Internet, it supposed to handle the display, but it didn't too. I though that my display is broken, but I attached it to Raspberry Pi and executed an Adafruit script - it worked without any problems.

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

#define PIN_SCE PB0
#define PIN_RESET PB1
#define PIN_DC PB2
#define PIN_SCLK PB3
#define PIN_MOSI PB4

enum
{
FUNCTION_SET = 0x20,
FS_CHIP_POWERDOWN = 0x4,
FS_EXTENDED_INSTR = 0x1,

DISPLAY_NORMAL = 0xc,
DISPLAY_BLANK = 0x8,
DISPLAY_ALL_ON = 0x9,
DISPLAY_INVERSE = 0xd,

DRAM_SET_X = 0x80,
DRAM_SET_Y = 0x40,
};

void BitTransfer(uint8_t Bits)
{
PORTB &= ~(1 << PIN_SCE);
int8_t i;
for(i = 7; i>=0; i--)
{
if((Bits >> i) & 1)
PORTB |= (1 << PIN_MOSI);
else
PORTB &= ~(1 << PIN_MOSI);

PORTB |= (1 << PIN_SCLK);
_delay_us(10);
PORTB &= ~(1 << PIN_SCLK);
_delay_us(10);
}
PORTB &= ~(1 << PIN_MOSI);
PORTB |= (1 << PIN_SCE);
}

void SendCommand(uint8_t Command)
{
PORTB &= ~(1 << PIN_DC);
BitTransfer(Command);
}

void SendData(uint8_t Data)
{
PORTB |= (1 << PIN_DC);
BitTransfer(Data);
}

int main(void)
{
DDRB |= (1 << PIN_SCE);
DDRB |= (1 << PIN_RESET);
DDRB |= (1 << PIN_DC);
DDRB |= (1 << PIN_SCLK);
DDRB |= (1 << PIN_MOSI);
PORTB = 0;
_delay_us(10);
PORTB |= (1 << PIN_RESET);
PORTB |= (1 << PIN_SCE);
_delay_us(10);

SendCommand(FUNCTION_SET | FS_EXTENDED_INSTR);
SendCommand(0xa5); // VOP
SendCommand(0x06); // temp coefficient
SendCommand(0x13); // BIAS
SendCommand(FUNCTION_SET);
SendCommand(DISPLAY_ALL_ON);
while(1)
{
}

return 0;
}


I power my AVR using 3.3v pinout of Raspberry Pi, same with the LCD. The AVR is working at 1 MHz frequency. When I disconnect the plug leading to the ground, a black line appears on the screen for a short while. I don't know, maybe it's a hardware problem?

• If it could be a hardware problem, how do you expect us to identify it since you didn't provide any schematic? – dim Jul 28 '16 at 21:53
• (a) "I wrote my own code to handle the LCD, it didn't work."-> (b) "I tried a code found somewhere on the Internet [...] but it didn't too."-> (c) "I attached it to Raspberry Pi [...] it worked without any problems." - Putting those 3 points together, then a h/w problem with your AVR-related h/w seems something you should investigate. Since you are new to AVR, don't start with LCDs. Start with the simple "blink LED" and work your way to more complicated things. If you want help with your h/w, pls supply the schematic and photos of your actual h/w. FYI the 3.3V power from the RPi is limited! – SamGibson Jul 28 '16 at 21:55
• Thanks for the diagram - it's not a normal schematic but it gives us some info. The lack of a decoupling capacitor for the MCU is obvious, but might not affect operation. Add one anyway. Check 3.3V current is within RPi limit. Lack of photos means we can't tell whether actual wiring matches that wiring diagram. As I said, if it was me, I would start with a "blink LED" program to start - allowing you to test that your MCU is running, at the expected clock rate, and can be programmed successfully. After that, I would use a 'scope to view the SPI signals when running "known good" LCD code. HTH. – SamGibson Jul 28 '16 at 23:07
• 4,7 uF capacitor added, no effect, but I'm leaving it pinned. Blinking LED test checked - it worked as I programmed. When you suggested to make sure if RPi can give sufficient current, I came up with using batteries instead of bothering with Raspberry. IMHO RPi should be able to power the AVR and the LCD, but why not try. – user3366592 Jul 28 '16 at 23:41
• OK - I've explained how I would approach debugging your problem, but others may have different approaches. Unfortunately normal EE.SE questions are not the best place to debug issues, one comment at a time. When you get more "reputation" (points) you can use the chat rooms and people who have enough time might take such a debugging session to chat (I've done it a couple of times). Note that the RPi 3.3V max current capability is documented - so you can use your multimeter to measure the current being drawn by your MCU and LCD to confirm compatibility, without guessing. Anyway, good luck! – SamGibson Jul 29 '16 at 2:24

are those the same commands that the other working examples used? My working examples (this one the raspberry pi is driving the display) have a few more commands/settings.

    spi_command(0x21); //extended commands
//    spi_command(0xB0); //vop
spi_command(0xBF); //vop
spi_command(0x04); //temp coef
spi_command(0x14); //bias mode 1:48
spi_command(0x20); //extended off
spi_command(0x0C); //display on

//experiment 1
if(1)
{
spi_command(0x80); //column
spi_command(0x40); //row
for(ra=0;ra<504;ra++) spi_data(ra);
}


You are probably running slow enough you dont need any delays, but you could sprinkle some more in, between everything basically the D/C change to the spi select to the first data change and so on.

Do you have a scope to examine the bus? You can feed the signals into the raspberry pi and sample very fast and save the data to use it as a logic analyzer (bare metal is easier/better but can possibly do it on linux as well). Or use any other microcontroller so long as it is faster than what you think you are bit banging.

You could also put leds on the lines and make the delays massive, slow enough for you to visually see what is happening in what order.

If the display works when using off the shelf code, but not with yours then clearly it is not the display. It is something your code is or isnt doing. It is a fairly easy spi target to mess with, no reading back is required just blasting stuff out.

You probably dont have the storage in the microcontroller, but do in the pi, you can also take another approach, take your bit bang code, simulate the gpio and make a log file of every state change. Then make the real program just blast those out. At least you can visually see

#include <stdio.h>

unsigned char port;

static void spi_delay ( void )
{
}

static void spi_dc ( unsigned int x )
{
if(x) port|=(1<<0);
else  port&=~(1<<0);
printf("0x%02X,\n",port);
}
static void spi_cs ( unsigned int x )
{
if(x) port|=(1<<1);
else  port&=~(1<<1);
printf("0x%02X,\n",port);
}
static void spi_clk ( unsigned int x )
{
if(x) port|=(1<<2);
else  port&=~(1<<2);
printf("0x%02X,\n",port);
}
static void spi_mosi ( unsigned int x )
{
if(x) port|=(1<<3);
else  port&=~(1<<3);
printf("0x%02X,\n",port);
}

static void spi_command ( unsigned int cmd )
{
unsigned int ra;
unsigned int rb;

spi_dc(0);
spi_cs(0);
spi_delay();
for(rb=cmd,ra=0;ra<8;ra++,rb<<=1)
{
spi_mosi((rb>>7)&1);
spi_delay();
spi_clk(1);
spi_delay();
spi_clk(0);
spi_delay();
}
spi_cs(1);
}

static void spi_data ( unsigned int data )
{
unsigned int ra;
unsigned int rb;

spi_dc(1);
spi_cs(0);
spi_delay();
for(rb=data,ra=0;ra<8;ra++,rb<<=1)
{
spi_mosi((rb>>7)&1);
spi_delay();
spi_clk(1);
spi_delay();
spi_clk(0);
spi_delay();
}
spi_cs(1);
}

int main ( void )
{
port=0;

spi_clk(0);
spi_data(0);
spi_cs(0);
spi_cs(1);

spi_command(0x21); //extended commands
...


for that init and command giving this output:

0x00,
0x01,
0x01,
0x01,
0x05,
0x01,
0x01,
0x05,
0x01,
0x01,
0x05,
0x01,
0x01,
0x05,
0x01,
0x01,
0x05,
0x01,
0x01,
0x05,
0x01,
0x01,
0x05,
0x01,
0x01,
0x05,
0x01,
0x03,
0x01,
0x03,
0x02,
0x00,
0x00,
0x04,
0x00,
0x00,
0x04,
0x00,
0x08,
0x0C,
0x08,
0x00,
0x04,
0x00,
0x00,
0x04,
0x00,
0x00,
0x04,
0x00,
0x00,
0x04,
0x00,
0x08,
0x0C,
0x08,
0x0A,


Note from the code I have bit 0 as D/C, bit 1 is cs, 2 is clk, and 3 mosi. didnt do reset in this code, this one just tied reset to power I think, you can easily adjust those.

You could take that output then feed it into some code you run on the pi or microcontroller (if you have the space).

for(x=0;x<datalen;x++)
{
PORT = data[x];
delay();
}


You can create some tools to visually see the waveforms (I recommend looking at the very simple vcd format and using gtkwave). or even better just print them out as binary and turn your head sideways.

You can at least get a feel that you have things wired up right and you are bit banging right, once that works then you replace the set/clear functions for each bit with direct access to the gpio and move the code into the microcontroller. can even write it such that it compiles both ways with an abstraction layer.

If you have a multi channel scope you can save yourself a ton of work. Bit banging spi or i2c or mdio, etc you really really need a scope or need to build a logic analyzer out of a microcontroller or something. Scope is preferred, esp for things like i2c and mdio where you can see when the busses change directions and how fast/slow the pull ups are.

• now that I think about it I had a reset button on that microcontroller I borrowed that code from and that reset also went to the display so when I reset the mcu to either re-run the code or to go back to a bootloader to download the next experiment, the display was also reset. you can easily do what you were doing, make sure the reset is long enough, error on the side of too long for all of this, then shorten the delays later once it works (or once you actually measure them). – old_timer Jul 29 '16 at 2:39
• I "debugged" AVR output using my Raspberry and I could receive correct bytes. I'll try out your commands. – user3366592 Jul 29 '16 at 19:37
• just tossing out ideas, at the end of the day when bit banging you need to "see" what waveforms you are generating. And sometimes looking at your code and thinking you know what it is producing is not good enough, esp when it isnt working. Not everyone has access to a multi-channel scope, but maybe you have something else that can be used. – old_timer Jul 29 '16 at 19:38