文章目录

  • 1. LCD12864显示原理简单介绍
    • 1.1 液晶原理简单介绍
    • 1.2 LCD12864接口时序
  • 2. 代码实现
    • 2.1 引脚配置和延时函数
      • 2.1.1 LCD12864使用到的引脚配置
      • 2.1.2 使用到的延时函数
    • 2.2 LCD12864驱动相关代码
      • 2.2.1 串行发送一个字节的数据
      • 2.2.2 写命令
      • 2.2.3 写数据
      • 2.2.4 LCD12864初始化
    • 2.3 LCD12864显示字符串函数
    • 2.4 LCD绘图功能代码
      • 2.4.1 设置坐标函数
      • 2.4.2 清空屏幕函数
      • 2.4.3 绘图函数
      • 2.4.4 绘制任意点
      • 2.4.5 绘制水平线
      • 2.4.6 绘制垂直线

1. LCD12864显示原理简单介绍

1.1 液晶原理简单介绍

LCD屏幕上其实都是一个个的像素点组成的,每行每列都有若干个像素点。每个像素点所占据的数据位宽(或者说像素深度)对于不同类型的屏幕,是不一样的。比如真彩色屏幕,每个像素点占据24bit或者32bit的数据,也就是RGB888或ARGB8888(其中A表示透明度);而对于灰度屏(没错就是那种黑白电视)来说,1个像素点一般只占据8bit;而只有黑白两种颜色的黑白屏,只要1bit数据位即可表示。一般来说,占据的数据位越多,那么它能表示的颜色就越丰富,就越能真实呈现自然界中原本的颜色。屏幕像素点如下图所示:

对于LCD12864显示屏,则有128*64个像素点。我们要想在屏幕上显示出字符、文字甚至是图片等等,那么只要按照某种规律点亮相应的像素点就可以显示出相应的文字、字符或者图片,而这些按照某种规律点亮像素的数据就叫做点阵数据(对于图片的点阵数据一般叫做位图)。

扩展一下:其实对于点阵数据、图片的位图数据等,这一部分的数据处理是比较复杂的,比如数据矩阵转换、点阵数据反选等。另外,对于一幅图片的位图数据,数据量是非常庞大的。这里举个例子:对于分辨率是1920x1080,像素深度是24bit真彩色的一幅图片,它的大小就是:1920 x 1080 x 24bit = 5.93MB大小,也就是说一幅图片的大小就接近6MB的数据量了。那么如果是视频数据呢?那数据量可以大到惊人。但是我们生活中接触到的图片或者视频,其实都是经过压缩的,这里就使用了及其复杂的压缩算法,而要把图片或者视频呈现给我们时,就要按照压缩的规律进行解压缩,这又要涉及到解压缩算法。总之,对于这些数据的处理其实会涉及到很多的知识,感兴趣的小伙伴可以自己查找相关的资料学习。

下面我们使用一组8x16的字符A的点阵数据,解释一下这组数据怎么就能在屏幕上显示出’A’来。字符A点阵数据如下图所示:

其中,1代表点亮像素点,0代表不点亮。那么,我们把每个字节数据为1的位在8x16的像素格子上点亮,而为0的位不点亮。这样我们就可以在屏幕上呈现出一个字符’A’来。但是,有一个问题是,我们如何把这组点阵数据写到显示屏上(其实就是把数据写到显示屏里面的一块显存上)呢?

对于这一点,不同的屏幕,他们会使用不同的通信协议的,根据屏幕规定的通信协议要求,就可以往屏幕写入相应的点阵数据了,从而可以显示出自己想要显示的文字、图标、图片等等。因为本文驱动代码使用的显示屏是LCD12864,下面就简单介绍下LCD12864所使用的通信时序。

1.2 LCD12864接口时序

  1. 所用到的引脚

    一般LCD12864都有20个引脚的,这个查阅手册都可以了解到。所有引脚如下图所示:

    这里我所用到的是12864的串行通信方式,所以只用到一些与串行连接相关的引脚。LCD12864和我的STM32板子(我使用的型号是STM32F103VET6)的引脚连接如下:

    CLK — PA5

    SID — PA6

    CS — PA7

    PSB — GND

    对于12864电源和背光引脚,根据自己的屏幕要求接板子上的电源即可。这里要注意的是PSB这个引脚,这个引脚是串并行控制引脚,如果代码不想控制,直接接地即可选择为串行。另外,CS片选引脚如果代码也不想控制的话,那直接接VCC(3.3V)一直保持高电平也可以。

  2. LCD串行读写时序

    写代码时,这幅时序图是非常重要的,根据这幅时序图就可以对LCD12864进行数据读写了。从上面的时序图可以看出,要发送1个字节的数据,实际上是一共需要发送3个字节的数据,首先是读写命令控制,然后是发送数据的高4位,然后再发送数据的低4位,这样的一个过程才完成了一次写过程。

    关于LCD12864其他的一些数据缓冲区的地址位置,命令含义等这些内容,这里就不一一介绍了,这些其实都可以在手册查找到,我们还是直接上代码吧,从代码的注释中也可以了解到这些内容。

2. 代码实现

2.1 引脚配置和延时函数

2.1.1 LCD12864使用到的引脚配置

/*** @brief  LCD12864使用到的引脚配置* @param  无* @retval 无*/
void lcd_gpio_config(void)
{   GPIO_InitTypeDef  GPIO_InitStructure;/* 开启GPIOA时钟 */RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);/* PA7(CS), PA5(CLK), PA6(SID)引脚配置 */GPIO_InitStructure.GPIO_Pin = LCD_CS_GPIO_PIN | LCD_CLK_GPIO_PIN | LCD_SID_GPIO_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);/* 控制引脚初始状态全部输出低电平 */LCD_CS(0);LCD_CLK(0);LCD_SID(0);
}

2.1.2 使用到的延时函数

该延时函数只是简单的进行延时,不是非常精准。但是自己也是经过了测试的,自己对比过秒表,基本能对得上。

/*** @brief  us延时函数,stm32f1系列,72MHz频率,自己使用秒表对比过误差不大* @param  us: us延时时间* @retval 无*/
void delay_us(unsigned int us)
{unsigned short i = 0;while(us--){i = 6;while(i--);}
}/*** @brief  ms延时函数,stm32f1系列,72MHz频率,自己使用秒表对比过误差不大* @param  ms: ms延时时间* @retval 无*/
void delay_ms(unsigned int ms)
{unsigned short i = 0;while(ms--){i = 7995;while(i--);}
}

2.2 LCD12864驱动相关代码

2.2.1 串行发送一个字节的数据

/*** @brief  LCD12864串行发送一个字节数据* @param  byte: 一个字节数据* @retval 无*/
static void lcd_send_byte(unsigned char byte)
{unsigned char i;for(i=0; i<8; i++){LCD_CLK(0);LCD_SID(byte & (0x80 >> i));     // 按位发送    delay_us(5);     LCD_CLK(1);     }
}

2.2.2 写命令

LCD12864写命令或者写数据的过程,其实就是上面那一幅串行读写的时序图,时序图下面有详细的介绍,要发送一个字节的数据,实际上要发送3个字节的数据。第一个字节数是要告诉12864我要发送的是命令还是数据,接下来发送的两个字节数 ,是要把自己发送的命令/数据拆分为高4位和低4位发送(这个是时序图规定要这样发送的,不同规格的12864可能时序不一样,这里要注意一下)。

/*** @brief  LCD12864写命令* @param  cmd: 要发送的命令* @retval 无*/
static void lcd_write_cmd(unsigned char cmd)
{LCD_CS(1);delay_ms(1);                 lcd_send_byte(0xf8);        // 写命令lcd_send_byte(0xf0 & cmd);    // 写高4位指令lcd_send_byte(cmd << 4);     // 写低4位指令LCD_CS(0);
}

2.2.3 写数据

/*** @brief  LCD12864写数据* @param  data: 要发送的数据* @retval 无*/
static void lcd_write_data(unsigned char data)
{LCD_CS(1);delay_ms(1);                 // 这里如果延时时间太长,在绘图模式下,会出现严重的闪屏现象。我测试发现在100us以下就基本没有闪屏lcd_send_byte(0xfa);          // 写数据lcd_send_byte(0xf0 & data); // 写高4位数据lcd_send_byte(data << 4);   // 写低4位数据LCD_CS(0);
}

2.2.4 LCD12864初始化

LCD12864的初始化,其实它的规格书有一个初始化的流程图的,按照手册的流程图依次对12864进行初始设置即可。

/*** @brief  LCD12864初始化* @param  无* @retval 无*/
void lcd_init(void)
{ delay_ms(50);             // 上电自检延时lcd_write_cmd(0x30);           // 选择基本指令集,8bit数据流delay_ms(1);               lcd_write_cmd(0x0c);        // 开显示,关闭光标delay_ms(1);              lcd_write_cmd(0x01);        // 清除显示delay_ms(30);
}

2.3 LCD12864显示字符串函数

这里在LCD12864显示字符串的函数,使用的是12864内部自带的字库(包含了中文字库),而不是自己定义的点阵数据字库。内部自带的字库实际上就是DDRAM的地址,每个地址值所占据的像素点是16x16个像素点。自带字库的英文字符是8x16个像素点,中文字符是16x16个像素点,所以12864每行能显示的英文字符是16个,中文字符是8个,一共能显示4行。

/* LCD12864内部字库显示的DDRAM地址,每个地址占16*16个像素点 */
static unsigned char lcd_str_addr[4][8] = {{0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87},       {0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97},       {0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F},       {0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F}
};/*** @brief  LCD12864显示字符串* @param  column: 行地址0~3,row: 列地址0~7* @retval 无*/
void lcd_draw_str(unsigned char column, unsigned char row, const char *str)
{ /* 设置显示的行/列地址 */lcd_write_cmd(lcd_str_addr[column][row]); /* 逐个字符写入 */while(*str){ lcd_write_data(*str++);   }
}

2.4 LCD绘图功能代码

LCD12864内部还提供了一片绘图显示的存储空间,我们通过写入不同的数据在这片空间中,就可以显示出我们想要的不同内容了,比如可以画点、画线、绘制图片等。要往这片存储空间中写入数据,我们首先得让12864切换到扩展指令模式。话不多说,我们直接上代码。

2.4.1 设置坐标函数

首先我们设置的坐标是以LCD12864点阵像素点为单位的,一个像素点就是某一个位置的坐标。我手上的这块LCD12864显示屏,对于这块绘图空间,是分为上下半屏的,上半屏x轴起始地址是0x80,下半屏x轴起始地址0x88。另外,对于x轴,每增加一个地址,实际上是占据了16个像素点的,而对于y轴每增加一个地址,只占据一个像素点。代码如下:

/*** @brief  设置LCD12864显示坐标* @param  x: x轴坐标0~127, y: y轴坐标0~63* @retval 无*/
static void lcd_setXY(unsigned char x, unsigned char y)
{if (y >= 32){/* 下半屏 */lcd_write_cmd(0x80 + (y - 32)); // y坐标lcd_write_cmd(0x88 + (x>>4));  // x坐标}else{/* 上半屏 */lcd_write_cmd(0x80 + y);      lcd_write_cmd(0x80 + (x>>4));    }
}

就是y坐标大于等于32的时候,就处于下半屏了,因为上下半屏地址不一样,分类设置。另外x轴坐标是x>>4再增加的,这就是因为x轴一个地址就占据了16个像素点,所以需要右移4,实际上就是除以8。

2.4.2 清空屏幕函数

清空屏幕实际上就是往那块绘图空间发送0x00的数据,全部熄灭像素点即可达到清空屏幕的目的。

/*** @brief  清空屏幕* @param  无* @retval 无*/
void lcd_clear(void)
{unsigned char x, y; lcd_write_cmd(0x34);    // 切换到扩展指令集   for(y=0; y<64; y++){   lcd_setXY(0, y);    // 设置显示坐标for(x=0; x<8; x++){  /* 因为x轴一个地址对应16个像素点,可以连续发送两个字节数据 */lcd_write_data(0x00);lcd_write_data(0x00);}   }lcd_write_cmd(0x36);    // 打开绘图显示lcd_write_cmd(0x30);    // 回到基本指令集
}

这个函数实际上就是x轴循环8次,一次写入16字节的数据,所以在x轴就是每增加一次地址,就移动了16个像素点了(一定要注意这点,不然不好理解),所以只需要循环8次就够了。对于y轴每增加一次地址,只是移动到了下一个像素点,所以需要循环64次。

2.4.3 绘图函数

绘图函数其实和清空屏幕的函数原理是相似的,只不过绘图函数是往绘图存储空间写入的是图片的点阵数据(但是一定要注意所用的图片的点阵数据是符合这个显示屏的像素扫描方式的,上面也说过了,x轴一个地址对应16个像素点,y轴一个地址对应一个像素点)。代码如下:

/*** @brief  显示图片,注意显示起点坐标固定是(0, 0)* @param  无* @retval 无*/
void lcd_draw_picture(const unsigned char *data)
{unsigned char x, y; lcd_write_cmd(0x34);    // 切换到扩展指令集   for(y=0; y<64; y++){   lcd_setXY(0, y);    // 设置显示坐标for(x=0; x<8; x++){  lcd_write_data(*data++);lcd_write_data(*data++);}   }lcd_write_cmd(0x36);    // 打开绘图显示lcd_write_cmd(0x30);    // 回到基本指令集
}

2.4.4 绘制任意点

在绘图区域中,要绘制任意点,这需要一点点技巧。原因就是x轴一个地址对应16个像素点,我们需要在一次发送16个字节的数据中,根据x轴坐标点让这16个像素点的某一点像素点亮,而其他的像素点熄灭。代码如下:

/* x轴按位显示的位码表 */
static const unsigned char set_pix_bit[8] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};/* 显示缓冲区 */
static unsigned char disp_buff[8][64] = {0};/*** @brief  在LCD显示范围内绘制任意点x坐标范围是:0~127,y坐标范围是:0~63* @param  x: x轴坐标, y: y轴坐标, color: 颜色值,1:点亮像素,0:不点亮* @retval 无*/
void lcd_draw_dots(unsigned char x, unsigned char y, unsigned char color)
{lcd_write_cmd(0x34);       // 切换到扩展指令集 /* 超出显示范围,退出函数 */if ((x >= 128) || (y >= 64))return;/* 设置x, y坐标 */lcd_setXY(x, y);/* 填充显示缓冲区数据 */if (color == 1){/* 点亮某点像素 */disp_buff[x>>3][y] |= 0x00;    disp_buff[(x>>3)+1][y] |= set_pix_bit[x & 0x07];}else{  /* 熄灭某点像素 */disp_buff[x>>3][y] |= 0x00;disp_buff[(x>>3)+1][y] &= ~set_pix_bit[x & 0x07];}/* 输出数据到LCD显示 */if ((x >> 3) % 2 != 0)   // 判断x轴0~15个显示数据的奇偶性{/* 奇数 */lcd_write_data(disp_buff[x>>3][y]);lcd_write_data(disp_buff[(x>>3)+1][y]);}else{/* 偶数 */lcd_write_data(disp_buff[(x>>3)+1][y]);lcd_write_data(disp_buff[x>>3][y]);}lcd_write_cmd(0x36);        // 打开绘图显示lcd_write_cmd(0x30);        // 回到基本指令集
}

2.4.5 绘制水平线

把绘制任意点的函数写出来后,画线的函数就容易多了。代码如下:

/*** @brief  绘制水平线* @param  x0: x轴起点, y0: y0轴起点, x1: x轴结束点, color: 颜色值,1点亮像素,0不点亮* @retval 无*/
void lcd_draw_Hline(unsigned char x0, unsigned char y0, unsigned char x1, unsigned char color)
{/* 超出显示范围,退出函数 */if ((x0 >= 128) || (y0 >= 64) || (x1 >= 128))return;for ( ; x1>=x0; x0++)lcd_draw_dots(x0, y0, color);
}

2.4.6 绘制垂直线

/*** @brief  绘制垂直线* @param  x0: x轴起点, y0: y轴起点, y1: y轴结束点, color: 颜色值,1点亮像素,0不点亮* @retval 无*/
void lcd_draw_Vline(unsigned char x0, unsigned char y0, unsigned char y1, unsigned char color)
{/* 超出显示范围,退出函数 */if ((x0 >= 128) || (y0 >= 64) || (y1 >= 64))return;for ( ; y1>=y0; y0++)lcd_draw_dots(x0, y0, color);
}

以上就是我实现的LCD12864各个函数的功能了,这些代码我都是经过了测试的,我自己测试没发现什么bug,都是可以正常显示字符串、图片、绘制任意点、绘制线条等。不过我发现,在绘图模式下时,绘制图片、或者线条等,如果发送数据/命令那里延时时间太长的话,会出现严重的闪屏现象,把延时时间调整到100us以下时,就基本看不到闪屏的现象了。

我把完整的源码也上传了,有需要的小伙伴可以自行下载。源码传送门
如果有小伙伴在复现实验的过程中,发现有bug,欢迎指出。如果得不到实验的结果,也可以和我一起讨论。

STM32串行驱动LCD12864显示屏程序代码相关推荐

  1. STM32液晶显示HT1621驱动原理及程序代码

    1.HT1621电路分析 HT1621为32×4即128点内存映像LCD驱动器,包含内嵌的32×4位显示RAM内存和时基发生器以及WDT看门狗定时器. HT1621驱动电路如下图所示: 图1 与单片机 ...

  2. 痞子衡嵌入式:聊聊i.MXRT1170上串行NOR Flash双程序可交替启动设计

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT1170上串行NOR Flash双程序可交替启动设计. 在上一篇文章 <i.MXRT1060/1010上串行NOR F ...

  3. 痞子衡嵌入式:揭秘i.MXRTxxx系列上串行NOR Flash双程序可交替启动设计

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT500/600上串行NOR Flash双程序可交替启动设计. 在上一篇文章 <i.MXRT1170上串行NOR Fla ...

  4. 痞子衡嵌入式:揭秘i.MXRT1060,1010上串行NOR Flash冗余程序启动设计

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT1060,1010上串行NOR Flash冗余程序启动设计. 工业产品设计里经常会有冗余程序/备份程序设计的需求,因为在工业 ...

  5. STM32 12864串行驱动

    有些12864没有以下全部功能: DDRAM:(Data Display Ram),数据显示RAM,往里面写啥,屏幕就会显示啥. CGROM:(Character Generation ROM),字符 ...

  6. STM32 串行FLASH文件系统FatFs

    目录 一.Windows系统为例 二.文件系统的结构与特性 为什么要应用文件分配表? 三.FatFs文件系统 1- FatFs 文件系统源码介绍 2- FatFs在程序中的关系网 四.配置FatFs移 ...

  7. 51单片机三线串行驱动12864液晶

    以前写12864的液晶程序都是用的并行的方式,这种方式焊接起来很麻烦,而且占用的IO口比较多. 今天尝试使用串行方式来驱动该模块. 本程序是基于STC89C52的12864串行模式的程序,硬件电路连接 ...

  8. 快速复习51单片机的外部中断、计数/定时器中断和串行口中断,加代码实现

    目录 1.中断系统的概念 1.1中断的概念 1.2中断系统的概念 1.3中断系统的优点 1.4我们要说的三种中断源 2.外部中断 2.1 外部中断0 代码示范 2.2 外部中断 分析 3.计数/定时器 ...

  9. 【嵌入式STM32-05】STM32 外部中断 EXTI 基本原理|库函数程序代码|寄存器

    STM32外部中断 1.中断基本原理 中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行 中断 ...

  10. 【STM32】按键检测实验主要程序代码分析

    文章目录 main.c key.c key.h main.c #include "led.h" #include "delay.h" #include &quo ...

最新文章

  1. 【swjtu】数据结构实验7_Huffman编码
  2. 51nod 1490: 多重游戏(树上博弈)
  3. 学生卡变成普通卡_刚接触流量卡的小白看这一篇就够了!!!
  4. 0. 正规鞅的混沌及可料表示
  5. 3级调度 fpga_FPGA的软核、硬核、固核
  6. 前端学习(1907)vue之电商管理系统电商系统之渲染修改用户的表单的重置操作
  7. linux lanmp 安装教程,Linux 安装 lanmp
  8. Vue-tools.crx 及安装常见问题解决
  9. java objects_Java Objects-------------工具类使用
  10. 计算机英语 1000字论文范文,英语论文格式写作 1000字论文格式-免费论文范文
  11. 用matlab画旋转抛物面_MAELAB (1)画出旋转抛物面z=x^2 y^2 编程(2)matlab 画出锥面z=(x^2+y^2)^(1/2)编程...
  12. graphpad prism8教程柱状图_GraphPad 8.0 新功能:柱状图功能体验大优化!(附教学)...
  13. vscode 程序员鼓励师_把软萌程序猿鼓励师装进VScode里?最强交互彩虹屁,GitHub2.5k星,爱上写代码...
  14. Unity3D新手入门教程 (b站阿发) 总结框架笔记
  15. 主机安全 服务器windows
  16. 笔试 | 东方财富 2020 春季校园招聘后端开发在线笔试【Python】【C++】【字符串】【动态规划】
  17. 华为写代码的这13年,成为了我最宝贵的人生历程
  18. springboot整合jwt
  19. 快速做课秘籍,七天之内做出一门好课程
  20. 2014-11艺龙招聘笔试面试

热门文章

  1. 最精确的噪音测试软件,关于噪音测试App的选择与使用
  2. 21天学通java6 pdf_21天学通Java(第6版) PDF_IT教程网
  3. 蓝桥杯C语言基础题---01字串
  4. 开源的工业软件-面向生产控制环节
  5. RTI_DDS自定义插件开发 1
  6. 读《亿级流量网站架构核心技术》
  7. SpringBoot整合shiro框架(张开涛跟我学shiro-综合实例-代码新版)
  8. 最好的MATLAB入门教程
  9. ipq4019 kernel 报错spi-nand spi0.1: spi transfer failed: -110
  10. Zalo电脑版多开软件