Problems with WH1602B LCD initialization

取消
显示结果 
显示  仅  | 搜索替代 
您的意思是: 

Problems with WH1602B LCD initialization

4,321 次查看
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by pindonga123 on Tue Nov 12 18:58:06 MST 2013
Hello I need to help with the lcd wh1602b initialization. I did the code but it doesnt work. I send the datasheet of lcd display and my circuit of connections.Im using LPC1769. Does anybody help me please?

#include "LPC17xx.h"
#include "lpc17xx_gpio.h"
#include "LPC17xx_systick.h"

#define FUNCTION_SET 1<<5 | 1<<6 | 1<<7
#define ON_OFF_CONTROL 1<<2 | 1<<3 | 1<<4 | 1<<5
#define DISPLAY_CLEAR 1<<2
#define ENTRY_MODE_SET 1<<2 | 1<<3 | 1<<4
#define ENABLE 1<<1
#define CLEAR 1<<0 | 1<<1 | 1<<2 | 1<<3 | 1<<4 | 1<<5 | 1<<6 | 1<<7 | 1<<8 | 1<<10

volatile unsigned long SysTickCnt;

void set_display (void);
void set_pines (void);
void Set_SysTick (void);
void Systick_Handler (void);
void Delay (uint32_t tick);
void set_enable_pin (void);

int main (void)
{
set_pines ();
set_display ();
while (1)
{
}
return 0;
}

void set_pines (void)
{
GPIO_SetDir (2, 1<<0, 1);
GPIO_SetDir (2, 1<<1, 1);
GPIO_SetDir (2, 1<<2, 1);
GPIO_SetDir (2, 1<<3, 1);
GPIO_SetDir (2, 1<<4, 1);
GPIO_SetDir (2, 1<<5, 1);
GPIO_SetDir (2, 1<<6, 1);
GPIO_SetDir (2, 1<<7, 1);
GPIO_SetDir (2, 1<<8, 1);
GPIO_SetDir (2, 1<<10, 1);
}

void set_display (void)
{
Delay (50);
GPIO_ClearValue (2, CLEAR);         //Clear all pins
GPIO_SetValue (2, FUNCTION_SET);    //Write FUNCTION_SET Command 0011100
set_enable_pin ();                  //Prepare pin ENABLE to write next command
Delay (1);                          //Delay 1msec
GPIO_SetValue (2, FUNCTION_SET);    //Write FUNCTION_SET 0011100
set_enable_pin ();                  //Prepare pin ENABLE to write next command
Delay (1);                          //Delay 1msec
GPIO_SetValue (2, ON_OFF_CONTROL);  //Write ON_OFF_CONTROL Command 00001111
set_enable_pin ();
Delay (1);
GPIO_SetValue (2, DISPLAY_CLEAR);   //Write DISPLAY_CLEAR Command 00000001
set_enable_pin ();
Delay(2);
GPIO_SetValue (2, ENTRY_MODE_SET);  //Write ENTRY_MODE_SET Command 00000111
set_enable_pin ();
}

void Set_SysTick (void)
{
SYSTICK_InternalInit (1);
SYSTICK_Cmd (ENABLE);
SYSTICK_IntCmd (ENABLE);
}

void SysTick_Handler (void)
{
SysTickCnt++;
}

void Delay (uint32_t tick)
{
unsigned long systickcnt;
systickcnt = SysTickCnt;
while ((SysTickCnt - systickcnt) < tick);
}

void set_enable_pin (void)
{
GPIO_ClearValue (2, CLEAR);  //Clear all pins
Delay (1);                   //Delay 1msec
GPIO_SetValue (2,ENABLE);    //Put 1 in Enable pin
Delay (1);                   //Delay 1msec
GPIO_ClearValue (2, CLEAR);  //Clear all pins
Delay (1);                   //Delay 1msec
}
标签 (1)
0 项奖励
回复
11 回复数

4,160 次查看
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Pacman on Wed Nov 27 01:45:56 MST 2013

Quote: pindonga123
What manual can I see for learn about GPIO interrupts and External Interrupts.



The UM10360 covers everything. But to get an example on how to use it, you should always try looking in lpc175x_6x/Examples as the first thing.
In this case, look at Examples/GPIO/, you'll find a project called GPIO_Interrupt.

Basically using interrupts are more or less the same in all Cortex-M microcontrollers...
Find out what the name of the interrupt handler needs to be. Then write your interrupt handler:
void XXXXXX_IRQHandler(void)
{
/* clear interrupt pending bit here (eg. as the first thing). */
/* your code here */
}


... where XXXXXX is the name of the interrupt handler, for instance EINT3

In some cases, you might be using one interrupt routine for multiple interrupt sources. In such cases, you'll have to find out what caused the interrupt.


void XXXXXX_IRQHandler(void)
{
/* read all interrupt pending bits into a variable here. For instance, we'll call the variable 'interruptFlags' */
/* clear all the interrupt pending bits you just read; eg. all those that are set. */
if(interruptFlags & (1 << ...))/* check interrupt flag bits here, to find out if this interrupt occurred */
{
/* your code here */
}
if(interruptFlags & (1 << ...))/* check interrupt flag bits here, to find out if this interrupt occurred */
{
/* your code here */
}
...
}


In your startup code, you'll have to enable the interrupt in the NVIC.
It's a good idea to first disable the interrupt in the NVIC, then set up everything that needs to be set up, and finally enable the interrupt. If doing it this way, you'll make sure that no interrupts happen while you set it up.
NVIC_DisableIRQ(XXXXXX_IRQn);
... /* enable clock power for the peripheral if necessary (for instance a timer need it) */
... /* set up interrupt here, for instance a timer or GPIO interrupt */
NVIC_EnableIRQ(XXXXXX_IRQn);

0 项奖励
回复

4,160 次查看
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by pindonga123 on Sun Nov 24 08:54:51 MST 2013
Thanks Pacman. I learn a lot with you. In fact it wasnt my choice LPC MCU it was my college choice. In fact other university work with PIC. I dont know if im wrong but found a lot of information about PIC MCU in internet. In another university of my country, they are very backward and work even with 8051 in other provinces for example. Is my first time learning and programming MCU hence your advice are helpful and very good, because i wanna do it in the rigth way. Sometimes it's hard to me use registers and understand it. In fact we dont see nothing about registers only in theory but in practice nothing. It is a interesting and fabulous world that im discovering and I see a great a market to work in the future maybe. What manual can I see for learn about GPIO interrupts and External Interrupts.
0 项奖励
回复

4,160 次查看
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Pacman on Thu Nov 21 00:28:26 MST 2013
The pinouts might be disordered, yes. It is like this on many microcontrollers, but not all.
I've once tried PIC, but never got very far. Later I tried AVR, which was the first one I had success with.
After that, I moved on to ARM7, which is a little difficult.
After investigating a lot, I found out that the LPC series from NXP are very easy to work with and they are very fast as well.
In fact, they're the fastest low-cost microcontrollers you can get, and they have very many useful features.
-So this is mainly why I am working with them. None of the others can reach the speed they do, and the others are even priced higher.
But so far, NXP's support have been better than what I experienced elsewhere, this probably means more to me than the capabilities of the device.
The documentation is excellent.

I've been working with other CPU-types, such as Z80, Motorola 6502/6510, COP8, Motorla 68xxx, Freescale/IBM PowerPC.

But if you want to work with low-cost/low-power ARM microcontrollers, I believe that you won't find any better choice than NXP's LPC range.
If you at a later point want to work with Cortex-A, and NXP don't have the Cortex-A at that point in time, Freescale or Texas Instruments are probably both good choices. But if NXP decides to make a Cortex-A7 or A12 for instance, I'd definitely start by looking at the specifications for those microcontrollers. :)

As for your question on "calling an interrupt handler", there are several options.
The best would probably be to set up a "Software Interrupt" (SVC, Super-Visor-Call).
But it all depends on why you want a software interrupt.
If you want an interrupt every time a pin changes, you should investigate more about GPIO interrupts and External Interrupts.
If you want an interrupt every time a character is available on a UART port, you should read about the UART.
For a quick reference, you can open lpc175x_6x/Core/Device/NXP/LPC175x_6x/Include/LPC175x_6x.h and find the first occurence of "Exceptions". This is a list of exception-vectors (aka interrupt-vectors) available on your processor.
0 项奖励
回复

4,160 次查看
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by pindonga123 on Wed Nov 20 18:12:51 MST 2013
Thanks Pacman. Im learning a lot with you. It's my first time working with MCU and your advices are helpful. I have two question. I noticed that the pins in LPC1769 stick are very very disordered and not continuos the ports, which is a litlle uncomfortably. Have you ever worked with another MCU like PIC, Morotorola, Freescale, etc?. Are like LPC?. And the second question....Is there another way to call a handler interrupt, not using Timers?
0 项奖励
回复

4,160 次查看
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Pacman on Wed Nov 20 08:23:51 MST 2013

Quote: pindonga123
Thanks Pacman. I could resolve how to write the data bus. I change P2[10] by P2[9] and I did GPIO_SetValue (2, string_data<<2);
string_data is a variable type char



While that would work fine, you could make things much easier for yourself by changing the connections to...

[list]
  [*]P2_0  ->  DB0
  [*]P2_1  ->  DB1
  [*]P2_2  ->  DB2
  [*]P2_3  ->  DB3
  [*]P2_4  ->  DB4
  [*]P2_5  ->  DB5
  [*]P2_6  ->  DB6
  [*]P2_7  ->  DB7

  [*]P2_8  ->  RS
  [*]P2_9  ->  E
  [*]P2_10  ->  R/~W
[/list]

(You can use any value you want for RS and E, though)

Why?
Because you can write 8 bits in one go (byte access on the PIN register), so you do not have to bitshift the values at all.
This will make your code shorter and easier to maintain; it will also make it simpler and easier to understand.

Note: It might be a good idea if you connect R/~W to an I/O-pin rather than directly to GND, because at some point in time, you may want to read the status of the display, in order to make sure the display is ready to receive data.

So my solution would be something resembling this:
void lcdSendData(uint16_t aValue)
{
if(aValue & 0x0100)/* if writing control-data */
{
LPC_GPIO2->CLR = LCD_RW_MSK | LPC_RS_MSK;/* pull these low to write control-data */
}
else
{
LPC_GPIO2->CLR = LCD_RW_MSK;/* pull R/~W low in order to write data */
LPC_GPIO2->SET = LPC_RS_MSK;/* pull RS high to write to DDRAM/CGRAM */
}
QDELAY(0);/* wait tAS (0) ns */
LPC_GPIO2->SET = LCD_E_MSK;/* pull Enable high */
QDELAY(140 - 40);/* wait tPW - tDSW ns */
*(volatile uint8_t *)LPC_GPIO2->PIN = aValue;/* set the data value */
QDELAY(40);/* wait tDSW (40) ns */
LPC_GPIO2->CLR = LCD_E_MSK;/* make the display grab the data */
QDELAY(10);/* Wait tH (10) ns */
/* So far, we've spent at least 140 + 10 = 150 ns. */
/* Since tC is 1200 (the time between each rise on the Enable pin),
 * we'll still need to spend 1050 ns */
QDELAY(1200 - 10 - 140);/* Wait tC - tH - tPW ns */
}

(For timing details, refer to the datasheet, page 15)
The above code is untested. It might work right out-of-the-box, but most likely there will be compile-errors.
QDELAY is mentioned elsewhere in this thread, so I don't want to repeat it.

The advantages of this routine is that it only changes the pins that it's supposed to change; it does not change other pins.
Furthermore, if you will be using interrupts, this routine will not conflict with the interrupts, as all writes are single-access and atomic.
0 项奖励
回复

4,160 次查看
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by pindonga123 on Wed Nov 20 06:54:15 MST 2013
Thanks Pacman. I could resolve how to write the data bus. I change P2[10] by P2[9] and I did GPIO_SetValue (2, string_data<<2);
string_data is a variable type char
0 项奖励
回复

4,160 次查看
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Pacman on Mon Nov 18 10:34:02 MST 2013

Quote: pindonga123
I couldnt understand you very well about code tag. I changed it. Is it ok?



If you changed it in this thread, it does not seem that it has the desired effect yet.
Here is an example on how it could be used:

The following...

[color=#030][[/color][color=#030]code][/color]#include "LPC17xx.h"
#include "lpc17xx_gpio.h"
...
Delay (1); //Delay 1msec
}[color=#030][[/color][color=#030]/code][/color]

...Would look like...

#include "LPC17xx.h"
#include "lpc17xx_gpio.h"
...
Delay (1); //Delay 1msec
}


0 项奖励
回复

4,160 次查看
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Pacman on Mon Nov 18 10:26:59 MST 2013

Quote: pindonga123
I take you like a tutor.


I find it the best way to give some training in how to fix the problems.
Yes, Google is a good way to find the solution to a problem, but one day you come across a problem, which Google can't find any search results for; then it's a good thing if you know how to track down that kind of issue.


Quote:
I need send the string "HELLO" to lcd.



I figured you would go there sometime soon. :)


Quote:
Well I have to send character by character It's easy beacuse I save Hello in a string array and send one by one. But the problem is when I wanna send for example H to LCD. H in binary is 01001000....and I have to send it to P2[10] P2[8] P2[7] P2[6] P2[5] P2[4] P2[3] P2[2].



I'd like you to think once more about what I mean by the following part:

Quote: Pacman
Now a question to you...
Would it be possible to arrange the pins, so it would be easier for you to write software for the display?
Hint: Look at DB0...DB7



I'll give you a pointer:
First of all, take a look at section 8.5 (page 139) in UM10470. You'll see an overview of the GPIO registers.

After that, find table 95 on page 142, read what it says right below that table, starting with "Aside from the 32-bit long and word..."
This is an important part.

The solution will involve that you change the way your pins are connected to your display.
I'll tell you the solution tomorrow, if you haven't found it by then; but such things is more fun to find out by yourself.
0 项奖励
回复

4,160 次查看
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by pindonga123 on Mon Nov 18 08:18:24 MST 2013
I couldnt understand you very well about code tag. I changed it. Is it ok?
0 项奖励
回复

4,160 次查看
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by pindonga123 on Mon Nov 18 08:12:12 MST 2013
Thanks Pacman one more time. I'll remember your advice for future forum post. Sometime it seems that is easy to correct o criticize but not to help. I take you like a tutor. I think there are 1000 way to correct someone, you choose the best way. I know that i made some mistake when i posted in the forum but I learnt the lesson as soon as my problem I could resolve it. I uploaded in the other forum the complete code corrected it. Not include all your advice but It work. Now i have another problem ja ja. Im full of doubt ja ja. I need send the string "HELLO" to lcd. Well I have to send character by character It's easy beacuse I save Hello in a string array and send one by one. But the problem is when I wanna send for example H to LCD. H in binary is 01001000....and I have to sen it to P2[10] P2[8] P2[7] P2[6] P2[5] P2[4] P2[3] P2[2]. I thought save H in for exmaple string_data and after write (string_data & MASK_D7_D0). But again my problem because i only know use GPIO_SetValue and GPIO_ClearValue, Do I have to use registers and write once the value?
0 项奖励
回复

4,160 次查看
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Pacman on Thu Nov 14 17:55:34 MST 2013
I'll try and write this so you'll learn something in addition to getting help.
This time, I'll give you more pointers than previous and less solutions.
It will help you the day where we're all asleep and you need help instantly.
I'll also try and give a few hints on the cosmetic look of your code, plus how to make it a little more robust.
But first I'll give you some hints on how to post on forums. Other people would just point you to the stanard page "how to ask good questions". Please find this page using google and read it.

[list]
  [*]It is a good idea not to write "URGENT" and "HELP" in the subject field.
Everyone who post here do it for a reason; most people post because they need help and have run out of other options. I believe that all posts in this forum are important, otherwise, the poster would not spend valuable time writing them.

  [*]Please use the "CODE" tag around your source code snippets.
As I wrote earlier, you will be more likely to receive answers, if your post is readable and well formatted, because people usually don't want to read code, format the code and find the bugs all at once in their minds. Most brains can't handle those 3 tasks at the same time.
[/list]

Please fix those two issues by going to the top of this thread, then click the "Edit" tab (which is right next to the "View" tab), then change the subject and place a [color=#030][[/color][color=#030]code][/color] tag right before your code-snippet and [color=#030][[/color][color=#030]/code][/color] tag right after your code-snippet. You'll see that the editing tools also contains a button for this task.
Before you click "Save" or "Post", you can click the button with an 'Eye' on it, so you can see how it will look. If it still needs editing, click the eye-button again to go back to edit-mode and make the changes.


Now, about the code...
You've done a great deal of work, and you've also given quite good information in this thread.

I believe your #defines should be improved:
Hint: Always surround your values on the right hand side by parantheses. For instance...
Incorrect:
#define CLEAR 1<<0 | 1<<1 | 1<<2 | 1<<3 | 1<<4 | 1<<5 | 1<<6 | 1<<7 | 1<<8 | 1<<10


Better:
#define CLEAR (1<<0 | 1<<1 | 1<<2 | 1<<3 | 1<<4 | 1<<5 | 1<<6 | 1<<7 | 1<<8 | 1<<10)


The reason is that if you apply any further operations on the value, for instance...
mask = CLEAR * 2;
... you will not get the expected results. Instead you'd get bit 10 moved to bit 20.

Instead of defining CLEAR, I recommend you to define MASK:
#define MASK ((1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7) | (1 << 8) | (1 << 10))


To generate a bit-mask, you could create a short helper-define:
#define B(b) (1 << (b))

Notice there's a paranthese around the lowercase 'b'. Make this a habit, and you will be spared problems with pre/post increment/decrement.
To use that define...
#define MASK (B(0) | B(1) | B(2) | B(3) | B(4) | B(5) | B(6) | B(7) | B(8) | B(10))


-Did you notice that it's shorter and easier to overview ?

... Now if we reverse the order, the bits will be positioned approximately in the same order they appear in an integer value (lowest bit set gives you value 1, highest bit in a byte set gives you value 128).

#define MASK (B(10) | B(8) | B(7) | B(6) | B(5) | B(4) | B(3) | B(2) | B(1) | B(0))



If you define 'MASK' instead of 'CLEAR', the following code...
void set_pines (void)
{
GPIO_SetDir (2, 1<<0, 1);
GPIO_SetDir (2, 1<<1, 1);
GPIO_SetDir (2, 1<<2, 1);
GPIO_SetDir (2, 1<<3, 1);
GPIO_SetDir (2, 1<<4, 1);
GPIO_SetDir (2, 1<<5, 1);
GPIO_SetDir (2, 1<<6, 1);
GPIO_SetDir (2, 1<<7, 1);
GPIO_SetDir (2, 1<<8, 1);
GPIO_SetDir (2, 1<<10, 1);
}


...could be changed into:

inline void set_pins(void)
{
GPIO_SetDir(2, MASK, 1);
}


...because all those pins use the same port, so no need to keep calling GPIO_SetDir to mix the bits slowly.

Now, if you look at your "set_display" function, the first thing you do is to clear all pins.
That's fine.
Then you set the pins to "FUNCTION_SET" and  immediately clear this value and set the enable bit.
(This happens every time you call GPIO_SetValue followed by set_enable_pin())

Is that what you intended to do ?

I would recommend you to make a function like this:
void lcdControl(uint32_t aValue)
{
   // this function should handle setting the data, setting enable pin, etc. in correct order with proper delays as well.
}


-Then you'd only need to call this function 3 or 4 times.
extra delays can be applied outside the function, so right before writing ENTRY_MODE_SET, you could insert a Delay(1);

Your comment says...
"Write FUNCTION_SET 0011100"

Your define says:
 #define FUNCTION_SET 1<<5 | 1<<6 | 1<<7


FUNCTION_SET is correct if you use it with those pins.
But doing it this way is actually awkward; there is a better way.


Now, what I would expect your lcdControl function to do is the following:

void lcdControl(uint32_t aValue)
{
// 1: Output aValue to desired GPIO port pins.
// 2: It might be a good idea to ensure that the R/W pin and RS pins are set correctly.
// 3: Wait tDSW nanoseconds.
// 3: Set the E pin
// 4: Wait tH nanoseconds.
// 5: Clear the E pin
}


Timing:
Let's assume your microcontroller (LPC17xx) runs at the maximum clock speed (120MHz).
120MHz = 120000000Hz.
One clock cycle then takes approximately 1/120000000 second. That is 8.333 nanoseconds.
Now, if you are to wait 10 nanoseconds for instance, you don't call Delay(1), which takes approximately 10000000 nanoseconds, right ?
It takes at least one clock cycle to read or write a value from/to memory or hardware. In our case, we calculated that one clock cycle to take 8.3 nanoseconds (at least, because if running 100MHz it would take 10 nanoseconds, running 10MHz it would take 100 nanoseconds, etc).
To make sure the compiler won't optimize dummy memory-accesses, we can declare a variable 'volatile'.
So we could make a small function, which would wait approximately 42 nanoseconds:
void wait42ns()
{
volatile static uint8_tdummy;

(void) dummy;
(void) dummy;
(void) dummy;
(void) dummy;
(void) dummy;
}

...The above function will read the value of 'dummy' and throw away the result. (The result goes into the big empty 'void').

Generally, it's a bad idea to do such things, especially in C-code, because you don't know exactly what the timing result will be. The problem comes when you move the code to a faster microcontroller, where the delay will suddenly be much smaller, for instance on a 200MHz microcontroller, the delay will be only
5 nanoseconds per read (thus 25 nanoseconds in total).

A better solution could be:
void qdelay(uint32_t aTime)
{
volatile static uint8_tdummy;

while(aTime--)
{
(void) dummy;
}
}


Then have a #define in your project, which defines F_CPU.
#define F_CPU 120000000

#define QDELAY(a) qdelay(((a) * (F_CPU / 1000000)) / 1000)

...and use qdelay like this:
QDELAY(42);


Now a question to you...
Would it be possible to arrange the pins, so it would  be easier for you to write software for the display?
Hint: Look at DB0...DB7.
0 项奖励
回复