Thursday, November 29, 2018

How to Define Special Characters on LCD


Hi there. This post is somehow a continuation of previous LCD post however not related with hardware but only software. I will address the problems, mentioned in the previous post and mention how to define special characters on LCD. Although, this is really simple to do in high level platforms such as Arduino using libraries, unfortunately there are only few resources on the Internet about that. 

I will try to follow the sequence of events in the previous post.


Problem with the Pins
I could not write any character to the display initially. It came to my mind that the characters were actually written on the display but I couldn't see them. I had experienced this a long time ago. It's simple enough to connect the LCD contrast pin (3) to the ground over a resistor with 4.7K or with 2.2K value. I was going to do like that. I was still thinking like "it is working without any problem but I could not see it", therefore I changed the resistor with a potentiometer in order to adjust the contrast. Normally, backlight is also not needed in green LCDs. Therefore, I chose a green one. Anyway, I connected pin 15 of LCD to Vcc and pin 16 of LCD to GND.

The problem was still not solved, although I connected everything without any shortcuts. I have changed the line outb(0, BASE) at the end of the code to outb(255, BASE). I checked the voltage on the data pins but everything was fine with them. I increased the delay parameter to 3 seconds in order to read signals with multimeter and checked E and RS pins. E was flipping between high and low as expected but there was nothing on RS except plain high signal. Pin connections I used, are as follows:

PP Signal
DB25 Pin
Centronics Pin
IC In
IC Out
LCD Signal (Pin)
nStrobe(C0)
1
1
IC2_17
IC2_3
E (6)
nSelect (C3)
17
36
IC2_15
IC2_5
RS (4)
Data0 (D0)
2
2
IC1_2
IC1_18
D0 (7)
Data1 (D1)
3
3
IC1_4
IC1_16
D1 (8)
Data2 (D2)
4
4
IC1_6
IC1_14
D2 (9)
Data3 (D3)
5
5
IC1_8
IC1_12
D3 (10)
Data4 (D4)
6
6
IC1_17
IC1_3
D4 (11)
Data5 (D5)
7
7
IC1_15
IC1_5
D5 (12)
Data6 (D6)
8
8
IC1_13
IC1_7
D6 (13)
Data7 (D7)
9
9
IC1_11
IC1_9
D7 (14)

The source article, I used during the assembly of the circuit, shows pin 13 for nSelect signal. I misunderstood this part, because pin 13 is actually an input pin. In another source [ https://www.lammertbies.nl/comm/cable/parallel.html ], which I usually used, port directions were also drawn. I was trying to get an output from pin 13 not pin 36 and since pin 13 is an input pin it was always set. BTW, the width of the plastic part on the both ends of jumper cables (colored ones) is bigger than the distance of the pins in Centronics port. Therefore, I used wires in even numbered ports and jumper cables in odd numbered ports (example). First, I suspected a contact problem but if this was the case, the code would produce different results on each execution. I will also mention the relationship of signals and ports in further chapters.

By the way, there is a video on LCDs, that there is no upper limit for DELAY parameter:


The Problem with the Function lcdKomut()
This function was initially written as follows: 

void lcdKomut(unsigned char veri)    {
    outb(veri, BASE);
    outb(8   , CTRL);    // RS = 0; E = 1
    usleep(DELAY);
    outb(9   , CTRL);    // RS = 0; E = 0
    usleep(DELAY);
}

For some reason, each time after I sent a command with this function, following data was printed twice. For example, as I tried to print 'Testing' in first line and '123' in the second, it was actually printed 'TTesting' and '1123'. I thought, this caused because the parallel port controller cannot reset the pin fast enough. Even E pin is still not zero, the CPU was too fast compared to the controller and sends the data to the bus immediately. But I have observed the same behavior even with bigger values of DELAY parameter. I changed the value 9 to 1 in the code and problem was fixed. There is an image, I like about these situations:


Since the problem was solved, I didn't want to investigate further. Maybe I should examine incoming signals with an oscilloscope but as I said, I didn't want to deal with it.


Status and Control Signals of Parallel Port
Speaking of signals, I need to briefly mention status and control signals. Parallel port has three groups of signals: data, status and control. These signals are controlled by the BASE, BASE+1 and BASE+2 I/O ports, respectively. Data signals are simple: The parallel port pins between 2 and 9 are set/reset according to the byte written to the BASE port and they remain. Status pins can be used for input because they are designed to read the printers status, i.e. printer generates an interrupt request, runs out of paper or has a paper jam etc.

nStrobe and nSelect pins are in control signal group. These control signals exist to control the printer and they are "active low" signals. Therefore, the name of the pin is prefixed by "n" or "~". nStrobe is used as a clock signal when computer is transmitting data to printer. The computer has to set this signal each time the data pins are changed. nSelect is set when the printer is selected (to print). nStrobe and nSelect pins were controlled using 0. and 3. bits of the control register, which is mapped to 0x378 + 2 = 0x37A I/O port. nStrobe is reset by writing 1 to the control port and nSelect is reset by writing 8 to the control port.

LCD Commands and Defining Special Characters
I used some of the LCD commands without mentioning all of them. 3-4 commands are really enough while working on LCDs. I usually use this image working on LCDs: https://goo.gl/images/QHtKee. Many similar results can be found in Google images by searching "lcd commands". The most proper way, is always relying on the datasheet of LCDs however reading an 60 pages document to build some device quickly, is impossible. I uploaded a generic datasheet to my Google drive and will use it to program CGRAM and create my own character set in LCD.

LCD fonts are stored CGRAM and CGROM (character generator RAM). There is a DDRAM, which stores the codes of the characters shown in the LCD cells. When a data is written to DDRAM, CGRAM/CGROM is used as a look-up table to demonstrate the characters. Since CGRAM is writable, it allows the users to create their own character set. There are 6 bits reserved for addressing CGRAM according to the datasheet but I am not sure that all of these bits are used in a standard LCD. CGRAM address starts with the zero.
On the page 24 of datasheet, table of the LCD commands can be found on Table 6. I first need to specify with a command, which character I will write. To address CGRAM, I need to issue 0b01XX XXXX command. Those Xs are the address bits of the character. Therefore the command is 0x40 for the zeroth address. According to page 31, the data can be send with lcdVeri() function. The character to be defined, must be sent as 5-bit-wide bitmap data. This information is on the previous pages in datasheet. One important thing is, LCD has to be disabled while writing CGRAM.

I slightly changed my old code to define characters. Definitions, lcdVeri() and lcdKomut() functions are same. I have pasted main() below:

int main(int argc, char* argv[])    {
    int i;

    if(ioperm(BASE, 3, 1))    {
        fprintf(stderr, "Access denied to %x\n", BASE);
        return 1;
    }

    lcdKomut(0x38);    // 8 bit, 2 lines, 5x7 px
    lcdKomut(0x08);    // disable lcd

    const unsigned char specialchars[] = {
        // something like smiley
        0B01110, 0B10001, 0B11011, 0B10001,
        0B11011, 0B10101, 0B10001, 0B01110,
        // inverse of smiley
        0B10001, 0B01110, 0B00100, 0B01110,
        0B00100, 0B01010, 0B01110, 0B10001,
        // spades
        0B00100, 0B01110, 0B11111, 0B11111,
        0B10101, 0B00100, 0B01110, 0B00000,
        // clubs
        0B00000, 0B01110, 0B10101, 0B11111,
        0B10101, 0B00100, 0B01110, 0B00000,
        // heart
        0B00000, 0B00000, 0B01010, 0B11111,
        0B11111, 0B01110, 0B00100, 0B00000,
        // tile (diamond)
        0B00000, 0B00100, 0B01110, 0B11111,
        0B11111, 0B01110, 0B00100, 0B00000
    };



    lcdKomut(0x40);   // 0. CGRAM address
    for (i = 0; i <= 47 ; i++)
      lcdVeri(specialchars[i]);

    lcdKomut(0x01);    // clear the screen
    //lcdKomut(0x80);    // linefeed
    lcdKomut(0x0F);    // enable screen, cursor blink

    lcdVeri('D'); lcdVeri('e'); lcdVeri('n');
    lcdVeri('e'); lcdVeri('m'); lcdVeri('e');
    lcdKomut(0xC0);    // second line
    lcdVeri('1'); lcdVeri('2'); lcdVeri('3');
    lcdVeri(' '); lcdVeri(' '); lcdVeri(' ');

    // special characters
    lcdVeri(0x00); lcdVeri(0x01); lcdVeri(0x02);
    lcdVeri(0x03); lcdVeri(0x04); lcdVeri(0x05);
 
    outb(0, BASE);
    return 0;
}

With this code above, special characters are defined and printed on the display:

No comments:

Post a Comment