cancel
Showing results for 
Search instead for 
Did you mean: 

LCD on STM32F4 Discovery

fullcaos
Associate II
Posted on October 10, 2012 at 15:23

Hi, I recently started programming microcontrollers STM32F4, I tried an example to control an LCD screen, there are on the net? can you give me directions?

#lcd-stm32f4discovery #stm32f4-hd44780 #interface-anything-to-anything
20 REPLIES 20
Posted on October 31, 2012 at 01:42

On the scale of complexity these 16 x 2 displays are relatively simple.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
orn
Associate II
Posted on October 31, 2012 at 02:11

Hi Clive and thank you for your advice, looking at the datasheet of various LCD I realized that I have to set and reset the bit with the precise time (in milli seconds) but I do not know how to get a delay function that will give me a delay in milli seconds, can you give me a helping hand?

for example:

GPIO_WriteBit(GPIOx,PIN,Bit_SET);
Delay(100);
GPIO_WriteBit(GPIOx,PIN,Bit_RESET);

how can I build a delay function that loses exactly 100 milli seconds? I have thought of such a thing: while(Ncoun){ Ncoun--; } but how can I calculate the bond that exists between time and nCount?
orn
Associate II
Posted on October 31, 2012 at 10:14

Clive, I'm sorry, you had me already answered a question like that, you have given me a solution to the problem of the delay, but I had forgotten since then I have not applied the solution to my previous project!

the solution was

: ''

there should be a

y = mx + c

type relationship with the core clock''

you told me c is the call/setup/return overhead.but how to calculate C? for every person who has the same problem I found with the use of the scope this report, which I have yet to see well with the theory ...

void Delay(__IO uint32_t time)
{

/*Delay is in milliseconds*/

time=time*42000; 
while(time--)
{
}
}
with these parameters of clock:
* SYSCLK(Hz) | 168000000
*-----------------------------------------------------------------------------
* HCLK(Hz) | 168000000
*-----------------------------------------------------------------------------
* AHB Prescaler | 1
*-----------------------------------------------------------------------------
* APB1 Prescaler | 4
*-----------------------------------------------------------------------------
* APB2 Prescaler | 2
*-----------------------------------------------------------------------------

crt2
Associate II
Posted on November 02, 2012 at 11:47

Do not use software delays. First of all during software delay your processor cannot do anything else, secondly, most compilers do not even compile such closed loop and thirdly getting exact time will never work like that (what if you get interrupt during that loop?).

Use TIMERs. All microControllers have TIMER (TIM) functions which you can use to measure exact time - and to do something when that interrupt happens. They will break whatever processor is doing (interrupt) and execute code that is in the interrupt routine.

abebarker
Associate II
Posted on November 03, 2012 at 04:26

Hi. In the driver I wrote I use a timer to interrupt the processor when it is time to send something to the LCD.

This is how the timers are configured to generate an interrupt when they count the length of time.

void LCD_InitalizeRCC(){
//////////////// RCC Initialize /////////////////////
/* PCLK1 = HCLK */
//RCC_PCLK1Config(RCC_HCLK_Div1);
/* PCLK2 = HCLK */
//RCC_PCLK2Config(RCC_HCLK_Div1);
/* TIM2 clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
/* GPIO clock enable */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOx, ENABLE);
return;
}
void LCD_InitalizeNVIC(){
/////////////// Variables /////////////////
NVIC_InitTypeDef NVIC_InitStructure;
//////////////// NVIC Initialize ////////////////////
/* Set vector table */
#ifdef FLASH_BOOT
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x00);
#else
NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x00);
#endif
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
/* Enable TIM2 global Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
return;
}
void LCD_TIM_Config()
{
TIM_TimeBaseInitTypeDef TIM_InitStructure;
/* TIM configuration */
TIM_InitStructure.TIM_Period = SystemCoreClock/LCD_PROCESS_IO_FREQ;
TIM_InitStructure.TIM_Prescaler = 0;
TIM_InitStructure.TIM_ClockDivision = 0;
TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM2, ENABLE);
return;
}

Start sending a clock signal to the timer, in this case Timer2:

LCD_InitalizeRCC()

.

I want the Interrupt Controller to generate an interrupt every time Timer2 goes off:

LCD_InitalizeNVIC()

I want to timer to go off after 0.0001 seconds have passed:

LCD_TIM_Config()

This is how I get the time

The inverse of the period is frequency. So 1 divided by 0.0001 = 10000.

In the file HD447h I set the LCD_PROCESS_IO_FREQ = 10000.

When I set up the timer I have it count the System clock. How many system clock cycles are in 0.0001 seconds? 16800.

TIM_Period = 16800 = 168000000/10000 = SystemCoreClock/LCD_PROCESS_IO_FREQ

What that means is every time timer2 counts up to 16800 it tells the NVIC to generate an interrupt and call the function TIM2_IRQHandler();

The name ''TIM2_IRQHandler'' is named in startup_stm32f4xx.s, where all the interrupt vectors are named, in case you were wondering.

In the file stm32f4xx_it.c is where all the Interrupt handling routines are defined. That is where you will find the TIM2_IRQHandler() function.

This function is simple. It calls my function where I decide what data needs to be sent to the LCD: LCD_ClockTick()

Then it clears the bit in the NVIC that says something needs to be done for timer2. I tell the NVIC that an interrupt for timer2 is not pending any more, TIM_ClearITPendingBit(...) and then we are done with the Interrupt handling routine. Control will then return to the main() function until the next interrupt occurs.

It may seem a little strange if you are not used to dealing with interrupts because you cant really see in the code where TIM2_IRQHandler() is called from. It's like it comes out of nowhere and suddenly TIM2_IRQHandler() is running while everything else is temporarily suspended. It's the magic of interrupts! They just happen, once you set up the interrupt controller, that is. Once you have finished with the interrupt service routine you are returned to your regular scheduled programming, err... I mean your main() program.

So that is how every 0.0001 seconds I call my function LCD_ClockTick(). It's done through the magic of timers and interrupts.

________________

Attachments :

STM32F4_LCD_44780_Driver_source.zip : https://st--c.eu10.content.force.com/sfc/dist/version/download/?oid=00Db0000000YtG6&ids=0680X000006Hzxo&d=%2Fa%2F0X0000000bRd%2FWfpROLvJcs4rcOE7BGcwuT9TLLlQ7pM3VjamDX9Bu0Y&asPdf=false
orn
Associate II
Posted on November 03, 2012 at 14:35

I do not understand how you are using the breaks, I wanted to use them only when it's time to send data to the LCD and not in the start-up phase. I'm wrong?

but the initial configuration of the LCD do not do with the delay?

Delay helped me to this configuration:

http://www.microcontroller.it/images/tutorials/Elettronica/lcd/init8fc.png

abebarker
Associate II
Posted on November 05, 2012 at 12:56

I use the breaks (interrupts) to send everything to the LCD, even the initialization of the LCD. It makes the code smaller.

Keep in mind that this is all happening behind the scene. The user of this ''driver'' only needs concern themselves with the User functions:

LCD_ConfigurePort

LCD_Initalize

LCD_Home

LCD_Clear

LCD_Print

LCD_MoveCursor

LCD_MoveDisplay

LCD_DisplayOn

LCD_DisplayScroll

LCD_EntryIncrement

LCD_CursorOn

LCD_CursorBlink

LCD_MoveToPosition

LCD_CustChar

Here is what happens behind the scene in this ''LCD driver'':

Everything has to be clocked into the LCD with the enable pin. The enable pin must be high for a certain length of time and then low for a certain length of time. I don't want to waste all that time waiting for the LCD when the processor can be doing other things while its waiting for the enable pin toggling on and off.

I need some way to have a list of things that I send to the LCD. For instance, in my main program I want to send a string to the LCD but I don't want to wait 67200 clock cycles for every character in the string. I want to give the string to the ''driver'' that interfaces the LCD. The ''driver'' sends the string to the LCD character by character.

The way I make this ''driver'' take a whole string and send it piece by piece is I make a list of the characters that need to be sent. The list is called a queue. I push every character onto the queue one by one. Every thing that is sent to the LCD is pushed onto the queue.

Every time an interrupt occurs the LCD_ClockTick() function is called. This is where all the work of sending data to the LCD happens. It looks at the queue and starts sending what ever is on the top of the queue. Each item on the queue is a task.

There are different types of tasks; a WAIT, WriteData, WaitBusy and NO_TASK. Each of those has a Data part and an Iter part. The NO_TASK is the simplest. When I encounter it I don't look at the Data or Iter parts I only call a function to have it deleted. The WAIT task takes a number in the data field and if it is zero then it will delete it's self. If the data is not zero then it will subtract a 1 from the number and leave it's self on the stack so on the next interrupt it will still be on the top of the stack. The WriteData task contains the data that will be output to the port. If LCD bus is 8 bits wide then we output the full byte and toggle the enable pin high to let the LCD know there is data for it. Keep in mind that we set the interrupt to happen every time the enable pin needs to be toggled on or off. It will take two interrupts to send that byte. On the second interrupt we toggle the enable pin off and we are finished this that task so we delete it. If the LCD data bus is 4-bits wide that it takes 4 interrupts to send the high and low nibbles and latch each one into the LCD.

When I initialize the LCD I push each task onto the Queue but I do something a little tricky when the bus is 4 bits wide. When processing the tasks I check the if the bus width is 4 or 8 bits wide. If it is 4 bits then the data is usually sent in two parts, the upper nibble then the lower nibble. I can only send one nibble at a time so I force the iteration count of the task to be 2 there by sending only one nibble. This is a special case and only happens for initialization in 4-bit mode. The initialization is a special case. I insert WAIT task with the correct timing to give the LCD module enough time between commands to perform a proper initialization as indicated in the datasheet.

________________

Attachments :

STM32F4_LCD_44780_Driver_source.zip : https://st--c.eu10.content.force.com/sfc/dist/version/download/?oid=00Db0000000YtG6&ids=0680X000006HzyR&d=%2Fa%2F0X0000000bRc%2FlTD3VUIb9MJ28IbuWnvEhvyf.rrzbXntR0b.YN9hvIE&asPdf=false
orn
Associate II
Posted on November 05, 2012 at 21:45

wow, your driver is written really well, I have almost finished my that are much simpler but I wanted to write about my drivers to learn, however, give a look to your right in order to understand everything that you have done! I have much to learn about microcontrollers!

sajeed
Associate II
Posted on March 03, 2014 at 07:42

Hi barker.abraham

I have used this on STM32F4 board and works pretty well. Thanks for your code . Now I want to use this on STM32F0 board, I ported it but nothing works only dark spots on the upper line of the display. I am able to get the TIM2 interrupt may be something wrong with the RCC configuration. I have cross checked the pin connections an nothing wrong in it.

Can you please let me know where I may be going wrong.

Thanks and Regards

Sajeed

Raahul Jagannathan
Associate II
Posted on July 02, 2018 at 13:38

I am getting the below attached error, when I trying building this. What have I missed out on?

0690X0000060LZeQAM.png