How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

This is the fifth article in the series of “Relearning 51 Microcontroller” (click to view). Today, this article mainly discusses the usage of LCD1602 (including left and right movement, displaying Chinese characters) and the optimization of the time-slicing scheduler mentioned in the previous article (let’s see what new problems we encounter and what new gameplay we have.).

Introduction

Alright, let’s start with today’s topic. In the third article, we introduced the multi-tasking method of driving digital tubes. However, digital tubes can only display numbers. If we want to display English and Chinese characters, we must use a liquid crystal display (LCD). LCDs can be divided into character type and dot matrix type, among which character type LCDs, due to their low cost and ease of use, have become ideal substitutes for digital tubes. Today, we will discuss the driver program for character LCD1602.

LCD1602 Pin Definition

Before discussing the driver, let’s first look at the pin definition of LCD1602 as shown below.

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

From the figure, we know that the LCD has 16 pins, where pins 1 and 2 are the power supply for the LCD, pin 1 is GND, and pin 2 is VCC.

The last two pins are for the LCD backlight, where BLA connects to the positive power supply and BLK connects to the negative power supply to light it up, as shown in the figure below.

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

Of course, you can also use a transistor to control the backlight switch. Here, we directly power it on to turn on the backlight, without needing program control.

Pin 3 is the liquid crystal display bias (used to adjust the display contrast), which is generally connected to a variable resistor to adjust the value of the resistor to adjust the contrast of the LCD. This may be a bit difficult to understand, so please look at the gif below.

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

Note:Sometimes, if the contrast is not adjusted properly, the screen will not display characters.

The next pin, pin 4 (RS), is for register selection. 1 is for data register, and 0 is for command register.

Pin 5 (RW) is for read/write operation selection (1 means read, 0 means write).

Pin 6 (E) is the LCD enable signal, which is valid for reading/writing when it changes from high to low (i.e., falling edge).

Pins 7 to 14 are the 8-bit data bus, directly connected to the P1 port of the microcontroller. As mentioned above, one reason why character type LCD1602 is easy to use is that its pins can be directly connected to the microcontroller.

LCD1602 Control Commands

Now that we know the pin definitions, let’s look at the control commands of LCD1602, as shown in the table below.

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

There are a total of 11 commands, and we will briefly introduce them.

  • Command 1: 0x01 is the clear screen command, which returns the cursor to the top left corner of the screen.

  • Command 2: 0x02 returns the cursor to the top left corner of the screen.

    Summary: These two commands both return the cursor to the top left corner of the screen. The difference is that 0x02 does not clear the screen, as shown in the figure below.

    How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

  • Command 3: Cursor and display mode settings (generally set to 0x06).

    Where I/D is the address pointer increment or decrement selection bit. I/D=1 means increment the pointer by 1; I/D=0 means decrement the pointer by 1.

S is whether the character movement direction on the screen is valid. S=0 means no movement for the entire screen display; S=1 means movement for the entire screen display (I/D=1 means left move, I/D=0 means right move).

  • Command 4: Display on/off and cursor settings (generally set to 0x0c).

    D is the overall display control bit. D=0 means turn off display; D=1 means turn on display.
    C is the cursor presence control bit. C=0 means no cursor; C=1 means there is a cursor.
    B is the cursor blinking control bit. B=0 means no blinking; B=1 means blinking.
  • Command 5: Cursor or character shifting (left shift is 0x18, right shift is 0x1c).

    S/C is the cursor or character shifting selection control bit. S/C=0 means move the cursor; S/C=1 means move the displayed character.
    R/L is the shift direction selection control bit. R/L=0 means left shift; R/L=1 means right shift.
  • Command 6: Function settings (generally set to 0x38).

    DL is the effective length selection control bit for data transmission. DL=1 means 8-bit data line; DL=0 means 4-bit data line.
    N is the line number selection control bit for the display. N=0 means single line display; N=1 means two-line display.
    F is the point matrix control bit for character display. F=0 means display 5*7 point matrix characters; F=1 means display 5*10 point matrix characters.
  • Command 7: CGRAM address setting (address range is 0~63, a total of 64 bytes).

  • Command 8: DDRAM address setting. The LCD has an internal data address pointer that can be accessed to display all 80 bytes of data in RAM. As shown in the figure below.

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

The data format for command 8 is 0x80 + address code.

  • Command 9: Read busy flag. BF=1 indicates that the LCD is busy, and at this time, the LCD cannot accept commands or data; BF=0 indicates that the LCD is not busy.

  • Command 10: Write data.

  • Command 11: Read data.

LCD1602 Driver Program

With the commands, how do we make the LCD display characters?

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

This also requires looking at the timing diagram in the LCD data manual, as shown below.

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

In the figure, the double-headed arrow points to the time, and the longest tc is also at the ns level, so we can use _nop_() for simple delays. _nop_() in the “intrins.h” header file takes one mechanical cycle, which is 12 clock cycles (the oscillation period of the crystal, 12M crystal is 1us).

Alright, let’s start writing the driver, the function for writing commands is as follows.

void delay_50us(){  unsigned int i;  for(i=50;i>0;i--){    _nop_();  }    }void lcd1602_write_cmd(unsigned char cmd){  LCD1602_E=0;  LCD1602_RS=0;//Select command  LCD1602_RW=0;//Select write    LCD1602_DATAPORT=cmd;//Prepare command  LCD1602_E=1;//Rising edge  _nop_(); //Delay   LCD1602_E=0;//Falling edge to write    delay_50us();  }
  • Pitfall Guide: Note that a 50us delay must be added at the end of the function; otherwise, some screens may display garbled characters.

The function for writing data is as follows:

void lcd1602_write_data(unsigned char dat) {  LCD1602_E=0;  LCD1602_RS=1;//Select data  LCD1602_RW=0;//Select write      LCD1602_DATAPORT=dat;//Prepare data  LCD1602_E=1;//Rising edge  _nop_();//Delay  LCD1602_E=0;//Falling edge to write    delay_50us();   }
  • Writing commands and writing data functions only differ in the value of LCD1602_RS in the second line.

Do you think this is enough?

Of course not, we still need to check if the LCD is busy; if it is busy, we cannot write data and commands.

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

The LCD busy check program is as follows:

void lcd_check_busy(){  u8 dt = 0xff;  do{    LCD1602_E=0;    LCD1602_RS=0;//Select command    LCD1602_RW=1;//Select read     LCD1602_E=1;    dt = LCD1602_DATAPORT;  }while(dt & 0x80);  LCD1602_E=0;}
  • This program uses a do{}while() loop. If the LCD is busy (for example, if it is not connected or has a fault), it will cause an infinite loop, causing the program to hang.

    How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

At this point, we need to define a counting variable. If the LCD is still busy after reaching a certain value, we exit the loop, as shown below:

void lcd_check_busy(){  u8 dt = 0xff;  u8 i = 0;//Counting variable  do{    LCD1602_E=0;    LCD1602_RS=0;//Select command    LCD1602_RW=1;//Select read    LCD1602_E=1;    dt = LCD1602_DATAPORT;    i++;//Increment count    if(i > 100){//Exit the loop after reaching a certain value      break;    }  }while(dt & 0x80);  LCD1602_E=0;}
  • By adding the counting variable i, we prevent the occurrence of infinite loops. Isn’t it simple? (* ̄︶ ̄)

Next, we can add the busy check in the write data or command function, as shown below:

void lcd1602_write_cmd(unsigned char cmd){  lcd_check_busy();  ...    }void lcd1602_write_data(unsigned char dat) {  lcd_check_busy();  ... }

Displaying Characters on LCD

With the driver functions, we can start displaying characters on the LCD1602.

First, we need an LCD initialization function as follows:

void lcd1602_init(void){  lcd1602_write_cmd(0x38);//8-bit data bus, 2 lines display, 5*7 point matrix character  lcd1602_write_cmd(0x0c);//Display function on, no cursor  lcd1602_write_cmd(0x06);//Cursor right move, display no move  lcd1602_write_cmd(0x01);//Clear screen}
  • It’s also very simple, only 4 commands are used.

Next, we will write a function to display a string as follows:

void lcd1602_show_string(u8 x,u8 y,u8 *str){  u8 i=0;  if(y>1||x>15)return;//Exit if row and column parameters are incorrect  if(y == 0){    lcd1602_write_cmd(0x80+i+x);//Display on the first line   }else{    lcd1602_write_cmd(0x80+0x40+i+x);//Display on the second line   }  while(*str){  //Print string      lcd1602_write_data(*str);//Display content    str++;//Pointer increment, display the next character  }}
  • This function is also very simple. The parameter y indicates which row, x indicates which column, and str is the pointer to the string to be displayed.

Finally, in the main function, we will display two lines of strings as follows:

void main(){  lcd1602_init();  timer2_init();  tasks_init();  lcd1602_show_string(0,0,"qian ru shi");  lcd1602_show_string(0,1,"xiao shu chong");  while(1){        task_progress();     }}

We will define a task to move the string to the left every 500ms as follows:

void lcd_task(){  lcd1602_write_cmd(0x18);}void tasks_init(){  //Add LCD left move task  add_task(500,&lcd_task);}
  • We can use the 0x18 command for left move (the right move command is 0x1c). The program effect is as follows:

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

Displaying Chinese Characters

Characters can be displayed, but how to display Chinese characters?

Haha, this requires the use of the CGRAM of the LCD1602.

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

CGRAM is the internal custom byte space of the LCD1602 (size is 64 bytes), and in the LCD1602, displaying a character (5*8 point matrix) requires 8 bytes. Therefore, the 64-byte CGRAM can only save 8 custom characters. We will utilize the custom byte space to achieve the effect of displaying Chinese characters.

So how does this 8-byte CGRAM save a character? This requires taking the modulus of the Chinese characters, as shown in the figure below.

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

Have you also noticed that the 5*8 point matrix can only display some simple Chinese characters? More complex ones won’t work, such as “嵌” character?

Is it really impossible?

Actually, there is a way to display it. We can split it into two 5*8 point matrices, as shown in the figure below.

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

Then we can take the modulus of this “嵌” character into two 5*8 point matrices.

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

Next, we will take the Chinese character “嵌入式小书虫” modulus and save it in an array as follows:

#define CHNESE_NUM 7char code chnese_data[8*CHNESE_NUM]={0x11,0x1f,0x0a,0x1f,0x0a,0x0e,0x0a,0x0e,//嵌0x02,0x1e,0x04,0x0f,0x15,0x04,0x0a,0x11,0x00,0x08,0x04,0x06,0x0a,0x11,0x11,0x00,//入0x09,0x04,0x1f,0x02,0x1d,0x09,0x1d,0x01,//式0x00,0x04,0x04,0x1d,0x15,0x05,0x0c,0x04,//小0x09,0x1f,0x0a,0x1f,0x09,0x09,0x0b,0x08,//书0x04,0x1f,0x15,0x1f,0x04,0x05,0x1f,0x01 //虫};

With this array, how do we write it to CGRAM?

We use command 7 to set the CGRAM address, and then write data into it, as follows:

void write_chnese_data(){  char i;  for(i = 0; i < 56; i++){    lcd1602_write_cmd(0x40+i);      lcd1602_write_data(chnese_data[i]);      }}
  • Since we have 7 5*8 point matrix character models, we need to write 56 bytes (one 5*8 point matrix requires 8 bytes).

Finally, we need to display the character models from CGRAM.

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

Don’t worry, this is mentioned in the LCD1602 manual. Since CGRAM can only save 8 custom character models, we can only write 0~7 to display the custom characters. The program for displaying “嵌入式小书虫” is as follows:

void show_chnese(){  lcd1602_write_cmd(0x80+2);    lcd1602_write_data(0);   lcd1602_write_data(1);  lcd1602_write_data(2);   lcd1602_write_data(3);  lcd1602_write_data(4);   lcd1602_write_data(5);  lcd1602_write_data(6);  }

The program is very simple, and the display effect of the Chinese characters is as shown in the figure below.

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

The displayed character “嵌” doesn’t look very good. If it were a left-right structured character, it might look better (* ̄︶ ̄). You can also think about why the second line of characters does not move when using the full-screen left move command (0x18) (how is it implemented? Feel free to leave a message).

Next is today’s surprise segment.

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

Today’s surprise segment is to optimize the code of the previous time-slicing scheduler to make it simpler and more elegant.

First, let’s talk about the problems we encountered. Although the LCD task only has one left move command, as shown below:

void lcd_task(){  lcd1602_write_cmd(0x18);}

However, this task, although simple, calls the busy check function in the lcd1602_write_cmd function, and the running time of this function is uncertain (we can only guarantee that it is less than 1ms because we exit the dead wait using a counting variable)

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

Let’s take a look at the task initialization function as follows:

void tasks_init(){  task_manage.taskNum = 0;  add_task(2,&smg_display);  add_task(1000,&smg_clock);  add_task(500,&lcd_task);}

There are a total of 3 tasks: the first runs every 2ms, the second every 1000ms, and the third every 500ms. In this way, every 1000ms (the least common multiple of 2, 1000, and 500 is 1000), these 3 tasks will run simultaneously. Note that when we say simultaneously, we mean that after running this task, we immediately run the next task (not sure if I explained it clearly), just like the year of the tiger is followed by the year of the rabbit.

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

Assuming each task runs for less than 1ms, but when all three tasks run together, it may exceed 1ms (the timer interrupts every 1ms). Of course, this won’t cause any major problems, but at least there will be some safety hazards. Can we eliminate it?

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

The answer is definitely yes.

Let’s take a look at the task adding function and task structure as follows:

typedef void fun_t(void);typedef struct {  char statue;//Running status  int cycle;//Running cycle  int count;//Counting variable  fun_t *run;//Task function}task_t;void add_task(int cycle,fun_t *run){  if(task_manage.taskNum < TASK_NUM_MAX){    task_manage.tasks[task_manage.taskNum].statue = 0;    task_manage.tasks[task_manage.taskNum].cycle = cycle;    task_manage.tasks[task_manage.taskNum].run = run;      task_manage.taskNum++;  }}

We find that the count in the task structure is not assigned in the task adding function add_task. We can use this variable to separate multiple tasks.

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

For example, we assign the count of the smg_display task to 1. Since its running interval is 2ms (i.e., it executes every 2ms), it will execute this task at 1ms, 3ms, 5ms, 7ms, etc. Therefore, the smg_display task executes at odd times, while the other two tasks execute at even times, thus separating the smg_display task, which will never execute at the same time as the other tasks.

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

Great, but what about the remaining two tasks?

Similarly, we assign the count of the smg_clock task to 2, and the lcd_task task to 4. This way, it works as shown in the table below:

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

The smg_clock task adds 1000ms each time, while the lcd_task task adds 500ms each time. So how does 998 come about? That’s because we assign the count of the smg_clock task an initial value of 2, meaning it starts counting from 2, adding to 1000 to execute the smg_clock task, which results in adding from 2 to 1000, giving us exactly 998.

Now that we know this, let’s look at the table above. Pay attention to the unit digits; smg_clock and lcd_task have 8 and 6 respectively, so they won’t execute together.

Now let’s modify the task adding function as follows:

void add_task(int cycle,int count,fun_t *run){  if(task_manage.taskNum < TASK_NUM_MAX){    task_manage.tasks[task_manage.taskNum].statue = 0;    task_manage.tasks[task_manage.taskNum].cycle = cycle;    task_manage.tasks[task_manage.taskNum].count = count;    task_manage.tasks[task_manage.taskNum].run = run;      task_manage.taskNum++;  }}
  • Just add a parameter count.

The task initialization function can be written as follows:

void tasks_init(){  task_manage.taskNum = 0;  add_task(2,1,&smg_display);  add_task(1000,2,&smg_clock);  add_task(500,4,&lcd_task);}

This way, we have eliminated the risk of multiple tasks running at the same time. Isn’t it simple? (* ̄︶ ̄)

Alright, today’s article ends here. Of course, there are still some shortcomings, so please point them out to me. I have also created a microcontroller learning group. Everyone is welcome to join and learn together (* ̄︶ ̄)

How to Display Chinese Characters on LCD1602 and Optimize Time-Slicing Scheduler

Original works are not easy. If you like my public account and find my articles inspiring,

please be sure to “like, collect, and forward.” This is very important to me. Thank you!

Welcome to subscribe to Embedded Little Bookworm

Leave a Comment

Your email address will not be published. Required fields are marked *