A GDI+ Based Character LCD Control

时间:2025-04-24 18:35:44
This is a renew.

A GDI+ Based Character LCD Control

by Conmajia

Character liquid crystal display (LCD) modules are widely used in electronic devices, instruments and handiworks. They are simple, flexible, clear to view, and low power. Fig.1 shows an example of character LCD used in an amateur project.

A GDI+ Based Character LCD Control
Fig.1 LCD in An Amateur Instrument

Very cool, isn't it? What if I told you that you could implement this hardware part as a software UI control and used it in your applications? To accomplish this, you have to get to know some of the hardware details.

Inside an LCD module, there are matrixed liquid crystal dots. Built-in controllers scan rows (COMn) and columns (SEGn) to activate dots to constitute characters and symbols. The process is shown in fig.2.

A GDI+ Based Character LCD Control
Fig.2 LCD Display Principle

One thing above all you should know is the register set that controls all display content of the control: display data RAM (DDRAM), character generator RAM/ROM (CGRAM/ROM).

The DDRAM is an 80-byte buffer which can hold up to 40×2 of display data. That is the largest size of a single LCD controller (HD44xxx series) supports. You change characters on the screen by changing bytes in the DDRAM. CGRAM/ROM are used to generate custom characters or symbols, or just simply load pre-defined character/symbols in the ROM. With the character generator, you can do lots tricks such as display animations, icons or Chinese characters.

With all the hardware knowledges you just learned, you can now have your "soft" LCDs. Fig.3 demonstrates one of my implementations.

A GDI+ Based Character LCD Control
Fig.3 Demo of My LCD Control

The LCD control is a standard WinForm control derived from the UserControl class.

public class DotMatrixLcd : UserControl

A 2-D array works as the DDRAM to store all characters to be displayed.

DotMatrixCharacter[][] characters;

The DotMatrixCharacter represents a single character in the DDRAM. I made it a Control derived class to easy my work. You are welcome to optimize this if you found it to heavy to use.

public class DotMatrixCharacter : Control

To generate raw character data, a CG class is defined as below.

public sealed class CharacterGenerator
{
    /// Get character data from DDRAM by address.
    public static byte[] GetDdram(byte address)
    {
        return charset[address];
    }
    /// Get character data from DDRAM to match the given character.
    public static byte[] GetDdram(char character)
    {
        return charset[(byte)character];
    }

    // 8 cgram chars
    static byte[][] cgram = new byte[8][];
    // for dummy
    static byte[] emptyChar ={ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
    /// Store character data in CGRAM registers by index.
    public static void SetCgram(byte[] data, int index)
    {
        if (data == null || data.Length != 8)
            return;
        if (index < 0 || index > 7)
            return;

        cgram[index] = data;
    }
    /// Get CGRAM character data by index.
    public static byte[] GetCgram(int index)
    {
        if (index < 0 || index > 7)
            return emptyChar;

        return cgram[index];
    }

    // 256x8 bytes (1024 bytes) characters
    static readonly byte[][] charset =
        {
            // 0000 0000
            new byte[]{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
            // 0000 0001
            new byte[]{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
// ...
    

Now all the data is ready. Now the next todo is on the canvas. To paint a character, a renderer is built in the DotMatrixCharacter control.

void drawBlocks(Graphics g)
{
    byte[] charData;

    // check source of char to display for CGRAM support
    switch (charSource)
    {
        case DisplaySource.CGRAM:
            if (cgramData == null || cgramData.Length != DOT_ROWS)
                // invalid data, draw empty
                // all 0x00
                charData = new byte[DOT_ROWS];
            else
                charData = cgramData;
            break;
        case DisplaySource.DDRAM:
        default:
            charData = CharacterGenerator.GetDdram(ddramAddress);
            break;
    }

    // ready to draw
    byte mask;

    for (int i = 0; i < DOT_ROWS; i++)
    {
        // if use mask = 0x01 (right to left)
        // the output will be vertical mirrored
        mask = 0x01 << (DOT_COLS - 1);

        for (int j = 0; j < DOT_COLS; j++)
        {
            if ((mask & charData[i]) == 0)
            {
                // 0 - empty
                if (circleBlock)
                    g.FillEllipse(
                        inactiveBrush,
                        j * (blockSize.Width + spacing),
                        i * (blockSize.Height + spacing),
                        blockSize.Width,
                        blockSize.Height
                        );
                else
                    g.FillRectangle(
                        inactiveBrush,
                        j * (blockSize.Width + spacing),
                        i * (blockSize.Height + spacing),
                        blockSize.Width,
                        blockSize.Height
                        );
            }
            else
            {
                // 1 - fill
                if (circleBlock)
                    g.FillEllipse(
                        activeBrush,
                        j * (blockSize.Width + spacing),
                        i * (blockSize.Height + spacing),
                        blockSize.Width,
                        blockSize.Height
                        );
                else
                    g.FillRectangle(
                        activeBrush,
                        j * (blockSize.Width + spacing),
                        i * (blockSize.Height + spacing),
                        blockSize.Width,
                        blockSize.Height
                    );
            }

            // next bit
            //mask <<= 1;
            // msb to lsb
            mask >>= 1;
        }
    }
}

With the built-in renderer, the final LCD module control can obtain the extensibility to switch between different display contents like character displays, graphic dot matrix display, etc.

Source Code & Demo Executive

Download 'em here:

Source Code

Demo

References

  1. How to Use Character LCD Module, chan@elm-chan.org

↑Go top