Interfacing an LCD to the STM32F4 Discovery – Part 4: Finishing Touches

In the last post we put together some driver code for the SerLCD 2.5 device and got it displaying a message in a test program. The SerLCD has quite a bit more functionality to it though and in this post we’ll add a few extra methods to the driver and then put together a bit of a demo program that will showcase a few of them.

The SerLCD provides access to much of the functionality of the underlying HD44780 LCD controller through a set of command codes described in the SparkFun data sheet. These are passed to the controller by first sending one of two different control codes, either 0xFE or 0x7C, followed by a command parameter byte. The control codes are shown in the table below.

Command Codes
Clear the screen 0xFE, 0x01
Move the cursor right one 0xFE, 0x14
Move the cursor left one 0xFE, 0x10
Scroll Right 0xFE, 0x1C
Scroll Left 0xFE, 0x18
Turn visual display on 0xFE, 0x0C
Turn visual display off 0xFE, 0x08
Underline cursor on 0xFE, 0x0E
Underline cursor off 0xFE, 0x0C
Blinking box cursor on 0xFE, 0x0D
Blinking box cursor off 0xFE, 0x0C

You can also adjust the backlight brightness by sending a different set of codes.

Command Codes
Backlight Off 0x7C, 0x80
Backlight 40% 0x7C, 0x8C
Backlight 73% 0x7C, 0x96
Backlight 100% 0x7C, 0x9D

In addition there are other codes that can be used to set up the SerLCD for different display widths and heights and to change the baud rate of the interface. I’ve not played with these but if you need them, you have the source code!

Updating the Driver Code

Well the driver code just wouldn’t be complete without some of these extra commands taken care of so I decided to implement them. To do this I added an additional four methods to the driver. These are as follows:

Function Description
SERLCD_WriteChar Writes a single character to the device
SERLCD_SendCommand Sends a command to the device
SERLCD_SetCursorPosition Sets the cursor position
SERLCD_SetBacklight Sets the backlight brightness

For the SendCommand and SetBacklight methods I created a set of constants that can be used to send the specific commands without having to refer back to the data sheet for the details.

I started by adding the new methods and constants to the stm32f4xx_serlcd.h file. The resulting code is given in the listing below. I declared the display commands as arrays of bytes and they are defined in the stm32f4xx_serlcd.c file. I took a slightly different approach for the backlight commands (I suppose I could have done them all the same way) and defined only the specific control bytes. The function prepends it with the correct control byte. If your OCD can’t handle the fact that they work differently feel free to go ahead and change it, I won’t be upset!

/**
******************************************************************************
* @file stm32f4xx_serlcd.h
* @author Jon Masters
* @version V1.0
* @date 13-July-2013
* @brief This file contains all the functions prototypes for the SerLCD 
* library.
******************************************************************************
*/

/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __STM32F4xx_SERLCD_H
#define __STM32F4xx_SERLCD_H

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx.h"

/* Exported constants ------------------------------------------------------*/
#define SERLCD_BufferLength ((uint16_t)0x0040)
#define SERLCD_Ok ((uint16_t)0x0000)
#define SERLCD_BufferOverrun ((uint16_t)0x0001)
#define SERLCD_BufferUnderrun ((uint16_t)0x0002)

/* Backlight levels ----------------------------------------------------------*/
#define SERLCD_Backlight_Off ((char)0x80)
#define SERLCD_Backlight_On40 ((char)0x8c)
#define SERLCD_Backlight_On73 ((char)0x96)
#define SERLCD_Backlight_On100 ((char)0x9d)

/* Display commands ------------------------------------------------------*/
extern const char SERLCD_CLRSCRN[]; /* Clear the screen */
extern const char SERLCD_MVCURSR[]; /* Move the cursor right one */
extern const char SERLCD_MVCURSL[]; /* Move the cursor left one */
extern const char SERLCD_SCROLLR[]; /* Scroll Right */
extern const char SERLCD_SCROLLL[]; /* Scroll Left */
extern const char SERLCD_DISPON[]; /* Turn visual display on */
extern const char SERLCD_DISPOFF[]; /* Turn visual display off */
extern const char SERLCD_UNDRCURSON[]; /* Underline cursor on */
extern const char SERLCD_UNDRCURSOFF[]; /* Underline cursor off */
extern const char SERLCD_BLNKCURSON[]; /* Blinking box cursor on */
extern const char SERLCD_BLNKCURSOFF[]; /* Blinking box cursor off */

/* Exported types ------------------------------------------------------------*/
/* Exported functions --------------------------------------------------------*/

/* Initialization and Configuration functions *********************************/
void SERLCD_Init();
uint16_t SERLCD_IsBufferFull();
uint16_t SERLCD_WriteChar(char c);
uint16_t SERLCD_WriteMessage(const char* text, uint16_t length);
uint16_t SERLCD_SendCommand(const char* cmd);
uint16_t SERLCD_SetCursorPosition(uint16_t row, uint16_t col);
uint16_t SERLCD_SetBacklight(char cmd);

#ifdef __cplusplus
}
#endif
#endif /*__STM32F4xx_SERLCD_H */

The updated code for the implementation file is below. The changes include adding the definition of the control character arrays and the declaration of the new methods. The only method in there that probably warrants further discussion in the SERLCD_SetCursorPosition method that can be called to set the position of the cursor in the display. It is at this location, regardless of whether you have either of the two cursors on or not, that any new text output will appear. The reason that you might want to change the implementation here is that the code currently only works for the 2×16 character display. The SerLCD board also supports 2×20, 4×16 and 4×20 displays. I did toy with the idea of updating the Init method to pass in the display size and then work around it but decided in the end to leave that as an exercise for you to try out. If you are looking to do this and you’re struggling let me know and I’d be happy to help adapt it.

/**
******************************************************************************
* @file stm32f4xx_serlcd.c
* @author Jon Masters
* @version V1.0
* @date 13-July-2013
* @brief This file provides firmware functions to initialise and write output
* to a SparkFun SerLCD module v2.5 using USART1 on an STM32F4 device.
*
@verbatim
===================================================================
##### How to use this driver #####
===================================================================
[..]
(#) This driver assumes that the SerLCD module is connected to USART1
and will therefore take ownership of the USART1 interrupt service routine.
(#) NOTE: There is a half second delay in starting up the SerLCD while it
displays a splash screen. This driver won't deal with that for you so make sure
you have something in place to cope with that.
(#) Start by calling the SERLCD_Init() method which will perform the necessary
initialisation.
(#) Call SERLCD_WriteMessage() to write output to the device.
(#) SERLCD_IsBufferFull returns a value greater than 0 if the write buffer is
full.
(#) To write a single character to the display use the SERLCD_WriteChar method.
(#) Commands can be sent using the SERLCD_SendCommand method. The commands
available are listed in the Display Commands section of the header file.
(#) SERLCD_SetCursorPosition sets the cursor to a specified position in the
display. Note that the order of parameters is row then column and the indexes
are zero based. Note also that currently, this driver only supports 2x16
displays. To use displays of other sizes you'll need to update the driver
code.
(#) The method SERLCD_SetBacklight sets the backlight level to one of the
values specified by constants in the header file section Backlight levels.
@endverbatim
*/

/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx_serlcd.h"
#include <string.h>
#include <malloc.h>

/* Private constants */
#define SERLCD_CTRLLENGTH ((uint16_t)0x0002)

/* Display commands ------------------------------------------------------*/
const char SERLCD_CLRSCRN[] = {0xFE, 0x01}; /* Clear the screen */
const char SERLCD_MVCURSR[] = {0xFE, 0x14}; /* Move the cursor right one */
const char SERLCD_MVCURSL[] = {0xFE, 0x10}; /* Move the cursor left one */
const char SERLCD_SCROLLR[] = {0xFE, 0x1C}; /* Scroll Right */
const char SERLCD_SCROLLL[] = {0xFE, 0x18}; /* Scroll Left */
const char SERLCD_DISPON[] = {0xFE, 0x0C}; /* Turn visual display on */
const char SERLCD_DISPOFF[] = {0xFE, 0x08}; /* Turn visual display off */
const char SERLCD_UNDRCURSON[] = {0xFE, 0x0E}; /* Underline cursor on */
const char SERLCD_UNDRCURSOFF[] = {0xFE, 0x0C}; /* Underline cursor off */
const char SERLCD_BLNKCURSON[] = {0xFE, 0x0D}; /* Blinking box cursor on */
const char SERLCD_BLNKCURSOFF[] = {0xFE, 0x0C}; /* Blinking box cursor off */

/* Private types -------------------------------------------------------------*/
typedef struct
{
    uint16_t size; /* The size of the buffer */
    uint16_t start; /* The index of the next character to send */
    uint16_t end; /* The index at which to write the next character */
    char* elems; /* The location in memory of the buffer */
} SERLCD_BufferTypeDef;

/* Private variables ----------------------------------------------------------*/
SERLCD_BufferTypeDef cb;

/* Private Methods -----------------------------------------------------------*/

/**
* Initialises the SERLCD driver. Must be called before any other calls to the
* buffer are made.
*/
void SERLCD_Buffer_Init()
{
    cb.size = SERLCD_BufferLength;
    cb.start = 0;
    cb.end = 0;
    cb.elems = calloc(cb.size, sizeof(char));
}

/**
* Call this if you need to free the buffer associated with the driver. If you
* do this, you'll need to call the Init method again before using it.
*/
void SERLCD_Buffer_Free()
{
    free(cb.elems);
}

/**
* Returns a value greater than zero if the circular buffer is full.
*/
uint16_t SERLCD_Buffer_IsFull()
{
    return (cb.end + 1) % cb.size == cb.start;
}

/**
* Returns a value greater than zewro if the circular buffer is empty.
*/
uint16_t SERLCD_Buffer_IsEmpty()
{
    return cb.end == cb.start;
}

/**
* Writes a character to the circular buffer. Returns either SERLCD_Ok
* or SERLCD_BufferOverrun depending on whether the character could be written
* or not.
*/
uint16_t SERLCD_Buffer_Write(char c)
{
    // check for a buffer overrun
    if (SERLCD_Buffer_IsFull())
    {
        return SERLCD_BufferOverrun;
    }
    else
    {
        cb.elems[cb.end] = c;
        cb.end = (cb.end + 1) % cb.size;
    }

    return SERLCD_Ok;
}

/**
* Reads the next character from the buffer. Returns SERLCD_Ok
* if the read happened or SERLCD_BufferUnderrun if the read would
* result in a buffer underrun.
*/
uint16_t SERLCD_Buffer_Read(char* c)
{
    // check for a buffer underrun
    if (SERLCD_Buffer_IsEmpty())
    {
        return SERLCD_BufferUnderrun;
    }
    else
    {
        *c = cb.elems[cb.start];
        cb.start = (cb.start + 1) % cb.size;
    }

    return SERLCD_Ok;
}

/* Public Methods -----------------------------------------------------------*/

/**
* Initialises the driver. Call this method before calling any other driver
* method.
*/
void SERLCD_Init()
{
    // Structures to hold the initialisation data
    GPIO_InitTypeDef GPIO_InitStruct;
    USART_InitTypeDef USART_InitStruct;
    NVIC_InitTypeDef NVIC_InitStruct;

    // enable the peripherals we're going to use
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);

    // Usart1 Tx is on GPIOB pin 6 as an alternative function
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(GPIOB, &GPIO_InitStruct);

    // Connect pin 6 to the USART
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_USART1);

    // fill in the interrupt configuration
    NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);

    // init the USART to 8:N:1 at 9600 baud as specified in the
    // SerLCD data sheet
    USART_InitStruct.USART_BaudRate = 9600;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    USART_InitStruct.USART_StopBits = USART_StopBits_1;
    USART_InitStruct.USART_Parity = USART_Parity_No;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStruct.USART_Mode = USART_Mode_Tx;
    USART_Init(USART1, &USART_InitStruct);

    // Enable USART1 peripheral
    USART_Cmd(USART1, ENABLE);

    // ensure USART1 interrupts are off until we have data
    USART_ITConfig(USART1, USART_IT_TXE, DISABLE);

    // prepare the buffer
    SERLCD_Buffer_Init();
}

/**
* Call this method if for some reason you want to free the memory
* associated with the driver.
*/
void SERLCD_DeInit()
{
    // disable the interrupts
    USART_ITConfig(USART1, USART_IT_TXE, DISABLE);

    // free the buffer
    SERLCD_Buffer_Free();
}

/**
* Returns a value greater than zero if the send buffer is already full.
*/
uint16_t SERLCD_IsBufferFull()
{
    return SERLCD_Buffer_IsFull();
}

/**
* Writes a single character to the SerLCD device. The method will return
* SERLCD_Ok if this is successful or SERLCD_BufferOverrun if it results in
* a buffer overrun.
*/
uint16_t SERLCD_WriteChar(char c)
{
    uint16_t rv = SERLCD_Buffer_Write(c);
    
    if (rv == SERLCD_Ok)
    {
        // enable the interrupt to send the message
        USART_ITConfig(USART1, USART_IT_TXE, ENABLE);
    }

    return rv;
}

/**
* Writes a string of characters to the SerLCD of a given length.
* The method will return SERLCD_Ok if this is successful or
* SERLCD_BufferOverrun if it results in a buffer overrun.
*/
uint16_t SERLCD_WriteMessage(const char* text, uint16_t length)
{
    // index into the character array
    uint16_t i = 0;

    // return value
    uint16_t rv = SERLCD_Ok;

    // write until we get to the end of the
    // text or overrun the buffer
    while (i < length && rv == SERLCD_Ok)
    {
        rv = SERLCD_Buffer_Write(text[i++]);
    }

    // did we add characters?
    if (i > 0)
    {
        // enable the interrupt to send the message
        USART_ITConfig(USART1, USART_IT_TXE, ENABLE);
    }

    return rv;
}

/**
* Clears the display and sets the write position to the top
* left position.
*/
uint16_t SERLCD_SendCommand(const char* cmd)
{
    return SERLCD_WriteMessage(cmd, SERLCD_CTRLLENGTH);
}

/**
* Sets the cursor to the specified row and column. Note that
* the row and column are both zero indexed. Currently this is
* set up for a 2x16 character display and will need to be
* adapted for other display sizes.
*/
uint16_t SERLCD_SetCursorPosition(uint16_t row, uint16_t col)
{
    char cmd[] = {0xFE, 0x80 + (row == 0 ? 0 : 64) + col};
    return SERLCD_WriteMessage(cmd, SERLCD_CTRLLENGTH);
}

/**
* Sets the brightness of the backlight to the specified level.
* Backlight level options are defined in the header file.
*/
uint16_t SERLCD_SetBacklight(char cmd)
{
    char c[] = {0x7C, cmd};
    return SERLCD_WriteMessage(c, SERLCD_CTRLLENGTH);
}

/*
* Handles all interrupts for USART1.
*/
void USART1_IRQHandler(void)
{
    // is this interrupt telling us that we can send a new character?
    if (USART_GetITStatus(USART1, USART_IT_TXE) != RESET)
    {
        // is there something for us to read?
        if (SERLCD_Buffer_IsEmpty())
        {
            // no, disable the interrupt
            USART_ITConfig(USART1, USART_IT_TXE, DISABLE);
        }
        else
        {
            // yes, get the next character from the buffer
            char c = 0x00;
            SERLCD_Buffer_Read(&c);
    
            // send it to the device
            USART_SendData(USART1, c);
        }
    }
}

So that’s that. We now have a driver that abstracts away a lot of the work of talking to a SerLCD connected display. It is interrupt driven so you don’t need to worry about that, it includes a circular buffer and also provides access to many of the control codes needed to create a really great display for your users. All we need now is some kind of demo program to show it off.

Implementing the SerLCD Driver Demo

In the previous post the code that drives the demo is all interrupt driven with only the initial configuration work carried out in the main method. This means that all of our demo code can also be written in the stm32f4xx_it.c file. I’ve included the main.c file below for completeness though so if you’ve dived straight into this post you don’t have to go searching for it.

/**
*****************************************************************************
**
** File : main.c
**
** Abstract : main function.
**
** Functions : main
**
** Environment : Atollic TrueSTUDIO(R)
** STMicroelectronics STM32F4xx Standard Peripherals Library
**
** Distribution: The file is distributed “as is,” without any warranty
** of any kind.
**
** (c)Copyright Atollic AB.
** You may use this file as-is or modify it according to the needs of your
** project. This file may only be built (assembled or compiled and linked)
** using the Atollic TrueSTUDIO(R) product. The use of this file together
** with other tools than Atollic TrueSTUDIO(R) is not permitted.
**
*****************************************************************************
*/

/* Includes */
#include "stm32f4xx.h"
#include "stm32f4_discovery.h"
#include "stm32f4xx_serlcd.h"

/* Private macro */
/* Private variables */
/* Private function prototypes */
/* Private functions */

/**
**===========================================================================
**
** Abstract: main program
**
**===========================================================================
*/
int main(void)
{
    /**
    * IMPORTANT NOTE!
    * The symbol VECT_TAB_SRAM needs to be defined when building the project
    * if code has been located to RAM and interrupts are used.
    * Otherwise the interrupt table located in flash will be used.
    * See also the <system_*.c> file and how the SystemInit() function updates
    * SCB->VTOR register.
    * E.g. SCB->VTOR = 0x20000000;
    */

    /* initialise SysTick with a 10ms period */
    RCC_ClocksTypeDef RCC_Clocks;
    RCC_GetClocksFreq(&RCC_Clocks);

    // 10ms tick
    SysTick_Config(RCC_Clocks.HCLK_Frequency / 100);

    /* initialise the SerLCD */
    SERLCD_Init();

    while (1){}
}

/*
* Callback used by stm32f4_discovery_audio_codec.c.
* Refer to stm32f4_discovery_audio_codec.h for more info.
*/
void EVAL_AUDIO_TransferComplete_CallBack(uint32_t pBuffer, uint32_t Size){
    /* TODO, implement your code here */
    return;
}

/*
* Callback used by stm324xg_eval_audio_codec.c.
* Refer to stm324xg_eval_audio_codec.h for more info.
*/
uint16_t EVAL_AUDIO_GetSampleCallBack(void){
    /* TODO, implement your code here */
    return -1;
}

Similarly the stm32f4xx_it.h file hasn’t changed either but as this remains as it is when a project is first created, I’ve not included it here. Instead we go straight onto the implementation file.

The way I’ve chosen to do this is by enumerating a set of program states that the demo iterates through. When it gets to the end it simply starts back at the beginning again. Well almost at the beginning, it misses out the first state which is to wait for the SerLCD splash screen to complete. There’s no need to do that again obviously!

I’m going to leave it to you to figure out the detail of how the demo works. There’s nothing in there that should be too difficult to work out and I think it shows off some of the capabilities of the driver quite nicely and hopefully gives you a few ideas of how you can use it yourself in your own projects.

/**
******************************************************************************
* @file Project/STM32F4xx_StdPeriph_Template/stm32f4xx_it.c 
* @author MCD Application Team
* @version V1.1.0
* @date 18-January-2013
* @brief Main Interrupt Service Routines.
* This file provides template for all exceptions handler and 
* peripherals interrupt service routine.
******************************************************************************
* @attention
*
* <h2><center>&copy; COPYRIGHT 2013 STMicroelectronics</center></h2>
*
* Licensed under MCD-ST Liberty SW License Agreement V2, (the "License");
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.st.com/software_license_agreement_liberty_v2
*
* Unless required by applicable law or agreed to in writing, software 
* distributed under the License is distributed on an "AS IS" BASIS, 
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************
*/

/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx_it.h"
#include "stm32f4xx_serlcd.h"

/** @addtogroup Template_Project
* @{
*/

/* Private typedef -----------------------------------------------------------*/
enum DEMO_STATE
{
    DS_WAIT_FOR_SPLASH,
    DS_SIMPLE,
    DS_SIMPLE_WITH_CURSOR,
    DS_SCROLL_LEFT,
    DS_SCROLL_RIGHT,
    DS_FLASH,
    DS_BACKLIGHT,
    DS_ERROR
} DemoState = DS_WAIT_FOR_SPLASH;

/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
static uint8_t DSWC_i = 0;

/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
void DoSimpleInit()
{
    SERLCD_SendCommand(SERLCD_CLRSCRN);
    SERLCD_SetCursorPosition(0, 0);
    SERLCD_WriteMessage("Simple text can ", 16);
    SERLCD_SetCursorPosition(1, 0);
    SERLCD_WriteMessage(" be displayed ", 16);
}

void DoSimpleWithCursorInit()
{
    SERLCD_SendCommand(SERLCD_CLRSCRN);
    SERLCD_SendCommand(SERLCD_BLNKCURSON);
    DSWC_i = 0;
}

void DoSimpleWithCursor()
{
    static char CursorMessage[] = "A cursor is used for user input ";

    if (DSWC_i < 32)
    {
        SERLCD_WriteChar(CursorMessage[DSWC_i]);
        DSWC_i++;
    }
}

void DoSimpleWithCursorDeinit()
{
    SERLCD_SendCommand(SERLCD_BLNKCURSOFF);
}

void DoScrollLeftInit()
{
    SERLCD_SendCommand(SERLCD_CLRSCRN);
    SERLCD_SetCursorPosition(0, 0);
    SERLCD_WriteMessage(" You can scroll ", 16);
    SERLCD_SetCursorPosition(1, 0);
    SERLCD_WriteMessage(" to the left ", 16);
}

void DoScrollLeft()
{
    SERLCD_SendCommand(SERLCD_SCROLLL);
}

void DoScrollRightInit()
{
    SERLCD_SendCommand(SERLCD_CLRSCRN);
    SERLCD_SetCursorPosition(0, 0);
    SERLCD_WriteMessage(" Or scroll to ", 16);
    SERLCD_SetCursorPosition(1, 0);
    SERLCD_WriteMessage(" the right ", 16);
}

void DoScrollRight()
{
    SERLCD_SendCommand(SERLCD_SCROLLR);
}

void DoFlashInit()
{
    SERLCD_SendCommand(SERLCD_CLRSCRN);
}

void DoFlash()
{
    static uint8_t FlashOn = 1;

    if (FlashOn)
    {
        SERLCD_SetCursorPosition(0, 0);
        SERLCD_WriteMessage(" Important info ", 16);
        SERLCD_SetCursorPosition(1, 0);
        SERLCD_WriteMessage(" can be flashed ", 16);
        FlashOn = 0;
    }
    else
    {
        SERLCD_SendCommand(SERLCD_CLRSCRN);
        FlashOn = 1;
    }
}

void DoBacklightInit()
{
    SERLCD_SendCommand(SERLCD_CLRSCRN);
    SERLCD_SetCursorPosition(0, 0);
    SERLCD_WriteMessage("Backlight level ", 16);
    SERLCD_SetCursorPosition(1, 0);
    SERLCD_WriteMessage(" is now at % ", 16);
}

void DoBacklight()
{
    static uint8_t BacklightLevel = SERLCD_Backlight_On100;
    static uint8_t Countdown = 0;

    if (Countdown == 0)
    {
        switch (BacklightLevel)
        {

        case SERLCD_Backlight_On40:
            BacklightLevel = SERLCD_Backlight_On73;
            SERLCD_SetCursorPosition(1, 11);
            SERLCD_WriteMessage("73% ", 4);
            break;

        case SERLCD_Backlight_On73:
            BacklightLevel = SERLCD_Backlight_On100;
            SERLCD_SetCursorPosition(1, 11);
            SERLCD_WriteMessage("100%", 4);
            break;

        case SERLCD_Backlight_On100:
            BacklightLevel = SERLCD_Backlight_On40;
            SERLCD_SetCursorPosition(1, 11);
            SERLCD_WriteMessage("40% ", 4);
            break;
        }

        SERLCD_SetBacklight(BacklightLevel);
        Countdown = 2;
    }

    Countdown--;
}

void DoErrorInit()
{
    SERLCD_SendCommand(SERLCD_CLRSCRN);
    SERLCD_SetCursorPosition(0, 0);
    SERLCD_WriteMessage(" You can do ", 16);
    SERLCD_SetCursorPosition(1, 0);
    SERLCD_WriteMessage(" this too! ", 16);
}

void DoError()
{
    static uint8_t FlashState = 0;

    if (FlashState == 0)
    {
        SERLCD_SetCursorPosition(0, 0);
        SERLCD_WriteChar('*');
        SERLCD_SetCursorPosition(0, 15);
        SERLCD_WriteChar(' ');

        SERLCD_SetCursorPosition(1, 0);
        SERLCD_WriteChar(' ');
        SERLCD_SetCursorPosition(1, 15);
        SERLCD_WriteChar('*');

        FlashState = 1;
    }
    else
    {
        SERLCD_SetCursorPosition(0, 0);
        SERLCD_WriteChar(' ');
        SERLCD_SetCursorPosition(0, 15);
        SERLCD_WriteChar('*');

        SERLCD_SetCursorPosition(1, 0);
        SERLCD_WriteChar('*');
        SERLCD_SetCursorPosition(1, 15);
        SERLCD_WriteChar(' ');

        FlashState = 0;
    }
}

/******************************************************************************/
/* Cortex-M4 Processor Exceptions Handlers */
/******************************************************************************/
/**
* @brief This function handles NMI exception.
* @param None
* @retval None
*/
void NMI_Handler(void)
{
}

/**
* @brief This function handles Hard Fault exception.
* @param None
* @retval None
*/
void HardFault_Handler(void)
{
    /* Go to infinite loop when Hard Fault exception occurs */
    while (1)
    {
    }
}

/**
* @brief This function handles Memory Manage exception.
* @param None
* @retval None
*/
void MemManage_Handler(void)
{
    /* Go to infinite loop when Memory Manage exception occurs */
    while (1)
    {
    }
}

/**
* @brief This function handles Bus Fault exception.
* @param None
* @retval None
*/
void BusFault_Handler(void)
{
    /* Go to infinite loop when Bus Fault exception occurs */
    while (1)
    {
    }
}

/**
* @brief This function handles Usage Fault exception.
* @param None
* @retval None
*/
void UsageFault_Handler(void)
{
    /* Go to infinite loop when Usage Fault exception occurs */
    while (1)
    {
    }
}

/**
* @brief This function handles SVCall exception.
* @param None
* @retval None
*/
void SVC_Handler(void)
{
}

/**
* @brief This function handles Debug Monitor exception.
* @param None
* @retval None
*/
void DebugMon_Handler(void)
{
}

/**
* @brief This function handles PendSVC exception.
* @param None
* @retval None
*/
void PendSV_Handler(void)
{
}

/**
* @brief This function handles SysTick Handler.
* @param None
* @retval None
*/
void SysTick_Handler(void)
{
    // we want the routine to run every 500ms and we're getting
    // called every 10ms
    static uint8_t delay = 50;

    delay--;

    if (delay > 0)
    {
        return;
    }

    delay = 50;

    // this is the state change countdown and each unit
    // is half a second
    static int16_t Countdown = 10;

    Countdown--;

    static uint8_t Init = 0;

    switch (DemoState)
    {
    case DS_WAIT_FOR_SPLASH:
        if (Countdown <= 0)
        {
            DemoState = DS_SIMPLE;
            Countdown = 5;
        }
        break;

    case DS_SIMPLE:
        {
            if (!Init)
            {
                DoSimpleInit();
                Init = 1;
            }

            if (Countdown <= 0)
            {
                Init = 0;
                DemoState = DS_SIMPLE_WITH_CURSOR;
                Countdown = 32;
            }
            break;
        }

    case DS_SIMPLE_WITH_CURSOR:
        {
            if (!Init)
            {
                DoSimpleWithCursorInit();
                Init = 1;
            }

            DoSimpleWithCursor();

            if (Countdown <= 0)
            {
                DoSimpleWithCursorDeinit();
                DemoState = DS_SCROLL_LEFT;
                Countdown = 14;
                Init = 0;
            }
            break;
        }

    case DS_SCROLL_LEFT:
        {
            if (!Init)
            {
                DoScrollLeftInit();
                Init = 1;
            }

            DoScrollLeft();

            if (Countdown <= 0)
            {
                DemoState = DS_SCROLL_RIGHT;
                Countdown = 14;
                Init = 0;
            }
            break;
        }

    case DS_SCROLL_RIGHT:
        {
            if (!Init)
            {
                DoScrollRightInit();
                Init = 1;
            }

            DoScrollRight();

            if (Countdown <= 0)
            {
                DemoState = DS_FLASH;
                Countdown = 10;
                Init = 0;
            }
            break;
        }

    case DS_FLASH:
        {
            if (!Init)
            {
                DoFlashInit();
                Init = 1;
            }

            DoFlash();

            if (Countdown <= 0)
            {
                DemoState = DS_BACKLIGHT;
                Countdown = 12;
                Init = 0;
            }
            break;
        }

    case DS_BACKLIGHT:
        {
            if (!Init)
            {
                DoBacklightInit();
                Init = 1;
            }

            DoBacklight();

            if (Countdown <= 0)
            {
                // restore the backlight back to 100%
                SERLCD_SetBacklight(SERLCD_Backlight_On100);
                DemoState = DS_ERROR;
                Countdown = 10;
                Init = 0;
            }
            break;
        }

    case DS_ERROR:
        {
            if (!Init)
            {
                DoErrorInit();
                Init = 1;
            }

            DoError();
        
            if (Countdown <= 0)
            {
                DemoState = DS_SIMPLE;
                Countdown = 10;
                Init = 0;
            }
            break;
        }
    }
}

/******************************************************************************/
/* STM32F4xx Peripherals Interrupt Handlers */
/* Add here the Interrupt Handler for the used peripheral(s) (PPP), for the */
/* available peripheral interrupt handler's name please refer to the startup */
/* file (startup_stm32f40xx.s/startup_stm32f427x.s). */
/******************************************************************************/
/**
* @brief This function handles PPP interrupt request.
* @param None
* @retval None
*/
/*void PPP_IRQHandler(void)
{
}*/
/**
* @}
*/

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

When you now run this code, you should see the display go through a number of demos including simple message display, scrolling, flashing and using the cursors. One thing I did have to do though, remember I said in the first article that I broke out the connection between the Discovery and the SerLCD in case I needed a pull-up resistor on the Tx / Rx link? Well when I ran it using an external power supply I got garbage on the screen because the pins on the Discovery started in an undefined state. I popped a 10K pull-up from the data line to the breadboard’s +5V line and it fixed the problem. I suspect you’ll need to do the same thing.

Well that’s the end of this series of posts. As always please let me know if any of this has been useful or not by commenting below. I’m always very keen to receive feedback and I’d be especially interested to know how you’ve used and modified this in your own projects.

Advertisements
2 comments
  1. Angel G said:

    Wonderful project and good coding style. And many topics covered: Interrupts, UART, SysTick, alt. pin function. The only thing I didn’t figure out is what dynamic memory management library did you use (to have the malloc.h / calloc() )

    • Hi Angel C, thanks for the feedback, it’s much appreciated. I’ve gotta be honest here, I don’t have a clue what specific memory management module was used. I just included malloc.h and let the Atollic IDE sort it all out. I just knocked up a quick test project to check it out and it seems to be ok. Malloc.h is picked up from ARMTools\arm-atollic-eabi\include\malloc.h and appears to refer to built in functions as far as I can tell. Hope this helps! Cheers, Jon

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: