51单片机--LCD1602显示自定义字符、HD44780驱动(含源码、含驱动程序、含指令集讲解)!!!好文推荐!
本文介绍了51单片机的LCD1602模块,包含电路图,内部HD44780驱动,指令集,以及讲解了LCD1602的驱动程序,演示了LCD显示字符串,LCD显示自定义字符的相关内容,十分推荐学习!!!
写在前面:
本文介绍了51单片机的LCD1602模块,包含电路图,内部HD44780驱动,指令集,以及讲解了LCD1602的驱动程序,演示了LCD显示字符串,LCD显示自定义字符的相关内容,十分推荐学习!!
如果对C语言的知识有所欠缺,请看看我之前C语言的相关博客;
如果阅读过程有些地方看不懂,建议先通读全文;
如果读者只需要51单片机驱动LCD1602显示字符串/自定义函数的源码;
建议:
1、4.6.1节为LCD1602显示字符串(源码)
2、5.3.1节为LCD1602显示自定义字符(源码)
或者在文末百度网盘有自定义函数的源码的三个.c文件
如果读者需要学习了解,相关代码的原理,包括驱动程序的书写,建议认真阅读这篇博客,相信你一定有所收获。如果能耐下心来,看完这篇文章,你一定注定是一个不平凡的读者。
博主熬夜总结!
目录
一、LCD1602介绍
LCD1602液晶显示器是广泛使用的一种字符型液晶显示模块。它是由字符型液晶显示屏(LCD)、控制驱动主电路HD44780等组成,主要用于显示字符型数据;
LCD1602液晶显示屏显示的范围为:行数(2行)、列数(16列),总共显示32个单元;
LCD1602的图片如下图所示:
LCD1602模块的电路图如下:
引脚的功能:
序号 | 符号 | 功能说明 |
1 | GND | 电源地 |
2 | VCC | 电源 |
3 | VO | 对比调整电压(液晶显示偏压) |
4 | RS | 指令/数据选择 |
5 | RW | 读/写选择 |
6 | E | 使能信号 |
7-14 | DB0-DB7 | 数据总线 |
15 | BG VCC | 背光电源正极 |
16 | BG GND | 背光电源负极 |
LCD1602如何工作?
在LCD1602内部中有一个控制驱动HD44780控制这整个LCD1602的工作方式,要想学好LCD1602必须学习好HD44780的控制方式;
二、HD44780介绍
在HD44780内部含有DDRAM、CGROM以及CGRAM;
名称 | 功能 |
DDRAM(显示数据RAM) | 用来寄存待显示的字符代码 |
CGROM(字符产生ROM) | LCD内部固化字模存储器,内部含有常用字符 |
CGRAM(字符产生RAM) | 用户自定义的字符产生存储器 |
2.1 DDRAM
DDRAM就是显示存储器,简单来说,如果想在LCD屏幕的某个单元(2行、16列)显示一个字符“X”,就需要在DDRAM的对应地址中,写入''X'';或者说要在屏幕上显示一个字符,需要完成三步。1、确定位置;2、写入字符;3、显示;DDRAM就相当于第三步显示;
那么如何确定DDRAM的位置与屏幕(2行、16列)的位置对应呢?
DDRAM共有80个字节,其地址与屏幕的对应关系:
屏幕只有2×16个单元,而DDRAM有2×20个地址;所以在使用时我们只是用前16个地址即可;(00H-0FH、40H-4FH);
2.2 CGROM
在确定完位置后,紧接着就是如何将字符写入对应的位置。
在LCD1602的内部CGROM中,已经为我们固化了字模,这里简单介绍一下字模;
字模:LCD1602是一个点阵屏幕,每个单元(2×16个单元)是由5行×7列的点阵组成,通过点阵的亮灭来显示不同的字符。
CGROM中已经内置了192中常用字符的字模,只需要将固定字模的对应代码放入DDRAM,即可在对应位置显示对应的字符;
例如:想在第1行第2列显示字符X,只需要将01111000(78H)写入DDRAM的01H即可。但是,在我们编写代码文件时,不需要进行一一对应,因为PC在编译的时候就会把X转化为78H,所以在电脑上编写代码时,直接输入X最终经过编译后就能够显示。
至于如何将对应代码写入DDRAM,在HD44780具有特定的指令集,后面我们会详细介绍相关的指令及其功能。
2.3 CGRAM
在LCD1602内部,除了已经固定的内置192个常用的字符的字模,存放在 CGROM中以外,另外还允许用户自定义8个字符,这些字符定义后存放在CGRAM中。CGRAM自定义字符的位置如下图。
如何在CGRAM中自定义字符?
共分为两个步骤:
1、将自定义的字符的代码写入CGRAM中;
2、将要显示的自定义字符的CGRAM的值传给DDRAM进行显示。
具体的做法,将在第5章:LCD显示自定义字符详细介绍;
三、HD44780指令集
在上面我们说了,显示一个字符需要,1、确定位置;2、写入字符;3、显示;那如何对DDRAM的内容和地址进行具体的操作呢?这时候需要用到HD44780的指令集;通过这些指令,完成我们对LCD1602液晶屏的基本操作;
在介绍指令之前,还要提及一些指令中涉及的名词;
AC:地址计数器,简单来说,里面存放的是地址信息,在执行完指令后,AC会进行改变或者不变(需要后面设定);
光标:用于确定位置的显示,聊天栏中一闪一闪的线。
3.1清屏指令
功能:1、清除液晶显示器,即将DDRAM的内容全部填入“空白”;
2、光标进行归位,即回到显示屏幕的左上角;
3、将AC的值归零;
3.2光标归位指令
功能:1、将光标撤回显示器左上角;
2、将AC的值归零;
3、保持DDRAM的值不变;
X指可0可1;
3.3模式设置指令
功能:设定每次进入1位数据后,光标的移动方向,并设定整体屏幕是否发生移动;
I/D:0=写入数据后光标左移;1=写入数据后光标右移;
S:0=写入数据后显示屏不移动;1=写入数据后显示屏整体右移;
3.4 显示开关控制指令
功能:控制器显示开关、光标是否显示以及光标是否闪烁;
D:0=显示功能关;1=显示功能开;、
C:0=无光标;1=有光标;
B:0=光标不闪烁;1=光标闪烁;
3.5设置显示屏或光标移动方向指令
功能:使光标移位或整个显示屏移位;
S/C | R/L | 设定情况 |
0 | 0 | 光标左移,且AC值减1 |
0 | 1 | 光标右移,且AC值加1 |
1 | 0 | 显示器所有字符左移,光标不动 |
1 | 1 | 显示器所以字符右移,光标不动 |
3.6功能设定指令
功能:设置数据总线位数、显示的行数以及字型;
DL:0=数据总线为4;1=数据总线为8;
N:0=显示1行;1=显示2行;
F:0=5×7点阵/字符;1=5×10点阵/字符;
3.7设定CGRAM地址指令
功能:设定下一个要存入数据的CGRAM的地址;
DB5DB4DB3 为字符号,共自定义8个字符,也就是显示字符的地址;
DB2DB1DB0为行号,共8行;
3.8设定DDRAM地址指令
功能:设定下一个要存入数据的DDRAM的地址;
3.9读取忙信号指令
功能:1、读取忙碌信号BF的内容,BF=1表示液晶显示器忙,无法再接受数据和指令;BF=0表示液晶显示器空闲,可以再接受数据和指令;2、读取AC中的值;
3.10数据写入DDRAM或CGRAM指令
功能;1、将字符码写入DDRAM,后面用于显示对应的字符;
2、将自定义的字符写入CGRAM.
3.11从DDRAM或CGRAM中读取数据指令
功能:从DDRAM或CGRAM中读取内容。
上面的11条指令就是完成LCD1602显示等操作的关键,当然,第一次见到这种指令可能会懵,但是,大家不要怕,下面用几个实例,来讲上述指令都用一下,就能更好的理解这些指令。
四、LCD显示字符串
4.1引脚配置
sbit LCD_RS=P2^6;//数据·指令选择端口
sbit LCD_RW=P2^5;//读·写 选择端口
sbit LCD_EN=P2^7;//使能端口
#define LCD_DataPort P0 //数据总线端口 P0(P0_0--P0_7)
根据开发板电路图,设置LCD1602的引脚与单片机核心端口的配置。
4.2写指令函数与写数据函数的定义
将上述11条指令,根据RS的值进行划分,RS=0(指令1-9)都是与指令的函数;RS=0(指令10-11)都与数据有关的函数。进行划分的主要作用是后面再对HD44780的指令集进行调用时,可以进行分类调用。
//写指令函数定义: LCD1602写指令函数
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS=0;//选择为指令,1为数据,0为指令
LCD_RW=0;//选择为写, 1为读,0为写
LCD_DataPort=Command;//写入指令的内容
LCD_EN=1; //使能脚E先上升沿写入
LCD_Delay(1);
LCD_EN=0; //使能脚E后负跳变完成写入
LCD_Delay(1);
}
// 写数据函数定义: LCD1602写数据函数
void LCD_WriteData(unsigned char Data)
{
LCD_RS=1; //选择为数据,1为数据,0为指令
LCD_RW=0; //选择为写, 1为读,0为写
LCD_DataPort=Data;//写入指数据的内容
LCD_EN=1; //使能脚E先上升沿写入
LCD_Delay(1);
LCD_EN=0; //使能脚E后负跳变完成写入
LCD_Delay(1);
}
4.3屏幕初始化
//初始化函数的定义:LCD1602屏幕初始化
void LCD_Init()
{
LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
LCD_WriteCommand(0x0f);//显示开,光标关,闪烁开
LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
LCD_WriteCommand(0x01);//光标复位,清屏
}
屏幕初始化用到的都是指令函数,在此处体现出的是4.2节指令与数据划分的必要性;指令函数的RS、RW的值是相同的,只需要确定DB7-DB0的值来区分不同的指令;
例如:LCD_WriteCommand(0x38);确定的是功能设定指令,设定8个数据接口,两行显示,5*7点针;DL=1;N=1;F=0;则DB7-DB0为:0011 10XX;故传递的数据为0X38;
再例如:
LCD_WriteCommand(0x0f);确定的功能是光标显示开,光标关,闪烁开; D=1;C=1;B=1;则DB7-DB0为:0000 1111;故传递的数据为0X0f;
4.4设置光标位置
void LCD_SetCursor(unsigned char Line,unsigned char Column)//(行数1-2,列数1-16)
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1));
}
else if(Line==2)
{
LCD_WriteCommand(0x80|(Column-1+0x40));
}
}
光标位置的确定能够为PC端传递数据时,确定好显示的位置,用光标的位置来作为显示字符位置的参考;
我们知道屏幕的位置为行为2行,列为16列;光标也是需要显示的,既然要在屏幕上显示,就要用到3.8指令,设定DDRAM地址;我们知道DDRAM共有四十个地址,如果是第一行,则传给DB7-DB0的应该为0x80|(Column-1);0x80是为了确定DB7为1,因为这是3.8指令规定的,使其按位与第一行的列数就是时间屏幕的地址(列数减一是因为第一行列数地址是00H-0fH,而我们说的列数是1-16);
4.5字符串显示函数的定义
// 字符串函数定义: LCD1602显示字符串
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
unsigned char i;
LCD_SetCursor(Line,Column);//光标的位置,确定字符串的起始位置;
for(i=0;String[i]!='\0';i++)
{
LCD_WriteData(String[i]);
}
}
字符串显示,只需要讲显示的内容以数据的形式传给端口;写入DDRAM中;DDRAM接受到数据代码后,与自身的CGROM固件库进行对照,将字模显示出来;
4.6字符串显示及源码
4.6.1源码
main.c文件
#include "LCD1602.h"
void main()
{
LCD_Init();
LCD_ShowString(1,2,"I LOVE YOU ");
LCD_ShowString(2,5,"hello,word!");
while(1)
{
}
}
LCD1602.h文件
#include "LCD1602.c"
void LCD_Init();
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
LCD1602.c文件(驱动代码)
#include <REGX52.H>
//引脚配置:
sbit LCD_RS=P2^6; // RS引脚为数据/指令选择 1为数据,0为指令
sbit LCD_RW=P2^5; // RW引脚为读/写选择 1为读,0为写
sbit LCD_EN=P2^7; // EN引脚为使能 1为数据有效,下降沿执行命令
#define LCD_DataPort P0 //定义P0引脚为数据端口
//延迟函数的定义;LCD1602延时函数,12MHz调用可延时xms;
void LCD_Delay(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
//写指令函数定义: LCD1602写指令函数
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS=0;//选择为指令,1为数据,0为指令
LCD_RW=0;//选择为写, 1为读,0为写
LCD_DataPort=Command;//写入指令的内容
LCD_EN=1; //使能脚E先上升沿写入
LCD_Delay(1);
LCD_EN=0; //使能脚E后负跳变完成写入
LCD_Delay(1);
}
// 写数据函数定义: LCD1602写数据函数
void LCD_WriteData(unsigned char Data)
{
LCD_RS=1; //选择为数据,1为数据,0为指令
LCD_RW=0; //选择为写, 1为读,0为写
LCD_DataPort=Data;//写入指数据的内容
LCD_EN=1; //使能脚E先上升沿写入
LCD_Delay(1);
LCD_EN=0; //使能脚E后负跳变完成写入
LCD_Delay(1);
}
//初始化函数定义: LCD1602屏幕初始化
void LCD_Init()
{
LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
LCD_WriteCommand(0x0f);//显示开,光标关,闪烁开
LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
LCD_WriteCommand(0x01);//光标复位,清屏
}
//LCD1602 进行清屏
void LCD_clear()
{
LCD_WriteCommand(0x01);
}
//设置光标位置
void LCD_SetCursor(unsigned char Line,unsigned char Column)//(行数1-2,列数1-16)
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1));
}
else if(Line==2)
{
LCD_WriteCommand(0x80|(Column-1+0x40));
}
}
// 字符串函数定义: LCD1602显示字符串
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=0;String[i]!='\0';i++)
{
LCD_WriteData(String[i]);
}
}
4.6.2实验现象
五、LCD显示自定义字符
在第四部分我们进行了LCD1602显示的CGROM中固有的字符,前面我们介绍过CGROM内置了192中常用字符的字模,这节我们介绍,如何在LCD1602液晶屏幕上显示自定义的字符。因为我们前面说过,在LCD1602内部,除了已经固定的内置192个常用的字符的字模,存放在 CGROM中以外,另外还允许用户自定义8个字符,这些字符定义后存放在CGRAM中。
如何在CGRAM中自定义字符?
共分为两个步骤:
1、将自定义的字符的代码写入CGRAM中;
2、将要显示的自定义字符的CGRAM的值传给DDRAM进行显示。
5.1存入自定义字符函数
那么如何设定CGRAM中的内容呢?首先我们要把所要编写的字符对应于5X8点阵 的“字模”提取出来,我们可以通过相关的软件来提取,也可以手工提取。说白了也就是将点阵的某一行中有显示的点用 1 表示,无显示的点用 0 表示,以此形 成该行对应的字模数据。
设定 CGRAM 的内容,要一行一行的设定,每一行对应一个 CGRAM,5X8 点阵, 每行 5 点,共 8 行,因此要将 8 行的字模数据都写入 CGRAM。写好后,就可像调 用 CGROM 字符一样来来调用它了。
例如:“日”这个字符,在5*8的点阵中的提取数据如下,0x0e代表的应该是8位数据,但是LCD1602的屏幕点阵为5*8,因此默认前三位都是0;这样它的数据应该是{00000000,00001110,00001010,00001110,00001010,00001010,00001110,00000000}对应的16进制数据为:{0x00,0x0e,0x0a,0x0e,0x0a,0x0a,0x0e,0x00};
//存入自定义字符函数:向LCD1602内部的CGROM中存入自定义字符
void LCD_USER_SetCustomChar( char *table,unsigned char num)
{
unsigned char i;
switch(num)//确定存入字符在CGRAM中的位置;8个自定义的初始位置(第一行的位置)
{
case 1: LCD_WriteCommand(0x40); break;
case 2: LCD_WriteCommand(0x48); break;
case 3: LCD_WriteCommand(0x50); break;
case 4: LCD_WriteCommand(0x58); break;
case 5: LCD_WriteCommand(0x60); break;
case 6: LCD_WriteCommand(0x68); break;
case 7: LCD_WriteCommand(0x70); break;
case 8: LCD_WriteCommand(0x78); break;
}
for(i=0;i<8;i++)
{
LCD_WriteData(table[i]);//将数据写入
}
}
上述代码是先确定CGRAM的地址,在进行数据(自定义字符)的输入; 那么,如果是第一个字符的(DB5DB4DB3为0)第一行(DB2DB1DB0)的位置应该是DB7-DB0为01000000,转化为16进制为0x40;
同理,如果是第八个自定义字符的(DB5DB4DB3为1)第一行(DB2DB1DB0)的位置应该是DB7-DB0为01111000,转化为16进制为0x78;
确定完地址后,其数据的写入也是一行一行的写入,上述写入用了一个数组,将数组中的内容依次写入。
for(i=0;i<8;i++)
{
LCD_WriteData(table[i]);//将数据逐行写入
}
5.2显示自定义字符函数
当数据写入CGRAM寄存器后,,接着就是将其进行调用显示,其代码于前面调用CGROM中的字符方式基本相同,只是需要注意自定义字符在CGRAM中的位置。此处的i确定的是自定义字符的字符数,也就是在CGRAM中的位置。
//显示自定义字符函数:LCD1602显示自定义字符
void LCD_USER_ShowString(unsigned char Line,unsigned char Column,unsigned char i)
{
LCD_SetCursor(Line,Column);
LCD_WriteData(i-1);
}
5.3实验显示
5.3.1源码
main.c文件
#include "LCD1602.h"
unsigned char table1[]={0x04,0x1F,0x04,0x1F,0x11,0x1F,0x0A,0x1B};//克
unsigned char table2[]={0x0A,0x1F,0x0A,0x0A,0x1F,0x0A,0x0A,0x1B};//共
unsigned char table3[]={0x04,0x0A,0x11,0x04,0x04,0x04,0x04,0x04};//个
unsigned char table4[]={0x00,0x0e,0x0a,0x0e,0x0a,0x0a,0x0e,0x00};//日
void main()
{
LCD_Init();
LCD_USER_SetCustomChar( table1,1);//table1字模放CGROM1
LCD_USER_SetCustomChar( table2,2);//table2字模放CGROM2
LCD_USER_SetCustomChar( table3,3);//table3字模放CGROM3
LCD_USER_SetCustomChar( table4,4);//table4字模放CGROM4
LCD_USER_ShowString(1,1,1);//在第1行第1列显示第一个自定义字模 table1字模
LCD_USER_ShowString(1,2,2);//在第1行第2列显示第二个自定义字模 table2字模
LCD_USER_ShowString(2,3,3);//在第2行第3列显示第三个自定义字模 table3字模
LCD_USER_ShowString(2,4,4);//在第2行第4列显示第四个自定义字模 table4字模
while(1)
{
}
}
lcd1602.h文件
#include "LCD1602.c"
void LCD_Init();
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_USER_ShowString(unsigned char Line,unsigned char Column,unsigned char i);
void LCD_USER_SetCustomChar( char *table,unsigned char num);
lcd1602.c文件(驱动程序)
#include <REGX52.H>
//引脚配置:
sbit LCD_RS=P2^6; // RS引脚为数据/指令选择 1为数据,0为指令
sbit LCD_RW=P2^5; // RW引脚为读/写选择 1为读,0为写
sbit LCD_EN=P2^7; // EN引脚为使能 1为数据有效,下降沿执行命令
#define LCD_DataPort P0 //定义P0引脚为数据端口
//延迟函数的定义;LCD1602延时函数,12MHz调用可延时xms;
void LCD_Delay(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
//写指令函数定义: LCD1602写指令函数
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS=0;//选择为指令,1为数据,0为指令
LCD_RW=0;//选择为写, 1为读,0为写
LCD_DataPort=Command;//写入指令的内容
LCD_EN=1; //使能脚E先上升沿写入
LCD_Delay(1);
LCD_EN=0; //使能脚E后负跳变完成写入
LCD_Delay(1);
}
// 写数据函数定义: LCD1602写数据函数
void LCD_WriteData(unsigned char Data)
{
LCD_RS=1; //选择为数据,1为数据,0为指令
LCD_RW=0; //选择为写, 1为读,0为写
LCD_DataPort=Data;//写入指数据的内容
LCD_EN=1; //使能脚E先上升沿写入
LCD_Delay(1);
LCD_EN=0; //使能脚E后负跳变完成写入
LCD_Delay(1);
}
//初始化函数定义: LCD1602屏幕初始化
void LCD_Init()
{
LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
LCD_WriteCommand(0x0f);//显示开,光标关,闪烁开
LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
LCD_WriteCommand(0x01);//光标复位,清屏
}
//LCD1602 进行清屏
void LCD_clear()
{
LCD_WriteCommand(0x01);
}
//设置光标位置
void LCD_SetCursor(unsigned char Line,unsigned char Column)//(行数1-2,列数1-16)
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1));
}
else if(Line==2)
{
LCD_WriteCommand(0x80|(Column-1+0x40));
}
}
// 字符串函数定义: LCD1602显示字符串
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=0;String[i]!='\0';i++)
{
LCD_WriteData(String[i]);
}
}
//显示自定义字符函数:LCD1602显示自定义字符
void LCD_USER_ShowString(unsigned char Line,unsigned char Column,unsigned char i)
{
LCD_SetCursor(Line,Column);
LCD_WriteData(i-1);
}
//存入自定义字符函数:向LCD1602内部的CGROM中存入自定义字符
void LCD_USER_SetCustomChar( char *table,unsigned char num)
{
unsigned char i;
switch(num)
{
case 1: LCD_WriteCommand(0x40); break;
case 2: LCD_WriteCommand(0x48); break;
case 3: LCD_WriteCommand(0x50); break;
case 4: LCD_WriteCommand(0x58); break;
case 5: LCD_WriteCommand(0x60); break;
case 6: LCD_WriteCommand(0x68); break;
case 7: LCD_WriteCommand(0x70); break;
case 8: LCD_WriteCommand(0x78); break;
}
for(i=0;i<8;i++)
{
LCD_WriteData(table[i]);
}
}
5.3.2实验现象
链接:https://pan.baidu.com/s/1nS-_ai1ZeXRhjWt0xLOFew
提取码:1022
写到此处,读到此处,已经超过10000字了,博主要为你点个赞,能够有耐心的将这篇博客阅读完,博主相信你一定未来会对单片机有一个质的飞跃,因为这篇博客的内容如此之多,你还是能够耐下心来的学习,让博主想起来“不积跬步无以至千里”,拥有这份耐心你未来一定会成功!!!
这篇博客的难度在于,里面穿插了好多LCD1602的驱动程序(LCD1602.C)文件,如果只是在应用层,那么CSND上面有太多的博客,将开发板的例程拿来直接引用。但是博主的这篇博客里面详细的介绍了,驱动程序的写法,如果你能看懂,那么你的能力就会有一个质的飞跃。加油!!!
创作不易,博主希望看到这里的你能够为博主点个赞,收藏一下,评论一下;这将是博主写下去的动力。 博主熬夜总结!!!
更多推荐
所有评论(0)