前言:
为了方便查看博客,特意申请了一个公众号,附上二维码,有兴趣的朋友可以关注,和我一起讨论学习,一起享受技术,一起成长。
1.概述
128X64的LCD可以显示 128 列64 行点阵单色图片,或显示 8 个/行4 行 1616 点阵的汉字,或显示 16 个/行8 行 8*8 点阵的英文、数字、符号。驱动IC为 ST7565R。在 LCD 上排列着 128×64 点阵,128 个列信号与驱动 IC 相连,64 个行信号也与驱动 IC 相连,IC 邦定在 LCD 玻璃上( COG工艺)。
2.硬件设计
2.1 连接原理图
硬件连接:
2.2 模块的引脚功能
模块通过 SPI 进行通信,需要的引脚如下图:
2.3 点阵与 DD RAM 地址的对应
**页定义:**PAGE,与平时所讲的“页”并不是一个意思,在此表示 8 个行就是一个“页”,一个 128*32 点阵的屏分为 8 个“页”,从第 0“页”到第 7“页”。
3.软件实现
3.1 模拟SPI时序
//头文件
#ifndef _SPI_H_
#define _SPI_H_
#include "nrf51.h"
#include "nrf_gpio.h"
#define SPI_CLK 2
#define SPI_CLK_LOW nrf_gpio_pin_clear(SPI_CLK)
#define SPI_CLK_HIGH nrf_gpio_pin_set(SPI_CLK)
#define SPI_MOSI 1
#define SPI_MOSI_LOW nrf_gpio_pin_clear(SPI_MOSI )
#define SPI_MOSI_HIGH nrf_gpio_pin_set(SPI_MOSI)
#define SPI_MISO 0
#define SPI_MISO_LOW nrf_gpio_pin_clear(SPI_MISO )
#define SPI_MISO_HIGH nrf_gpio_pin_set(SPI_MISO)
#define SPI_MISO_READ nrf_gpio_pin_read(SPI_MISO)
void Spi_Gpio_Init(void);
void Spi_Write_Byte(uint8_t dat);
uint8_t Spi_Read_Byte(void);
#endif
//源程序
#include "spi.h"
#include "nrf_delay.h"
#include "nrf_gpio.h"
#include <stdint.h>
#include "lcd128x64.h"
//定义SPI四钟方式的0和3
#define _CPOL 0
#define _CPHA 0
void Spi_Gpio_Init(void)
{
nrf_gpio_cfg_output(SPI_CLK);
nrf_gpio_cfg_output(SPI_MOSI);
nrf_gpio_cfg_input(SPI_MISO,NRF_GPIO_PIN_NOPULL);
#if _CPOL==0
SPI_CLK_LOW;
#else
SPI_CLK_HIGH;
#endif
}
//模式0:CPOL=0;CPAH=0
//模拟SPI写数据
#if _CPOL==0&&_CPHA==0
void Spi_Write_Byte(uint8_t dat)
{
uint8_t len;
for(len=0;len<8;len++)
{
SPI_CLK_LOW;//时钟上升沿采样传输数据
//nrf_delay_ms(1);
if(dat&0x80) //发送数据
{
SPI_MOSI_HIGH;
}
else
{
SPI_MOSI_LOW;
}
SPI_CLK_HIGH;
// nrf_delay_ms(1);
dat <<= 1;
}
}
//模拟SPI读数据
uint8_t Spi_Read_Byte(void)
{
uint8_t cnt;
uint8_t dat=0;
for(cnt=0;cnt<8;cnt++)
{
SPI_CLK_LOW; //下降沿后读取数据
nrf_delay_ms(1);
dat <<= 1; //最高位已经在数据线上了
if(SPI_MISO_READ) //
{
dat |= 0x01;
}
else
{
dat &= 0xfe;
}
//dat <<= 1;注:此处最高位被移除了
SPI_CLK_HIGH;
nrf_delay_ms(1);
}
return dat;
}
#endif
//模式3:CPOL=1;CPAH=1
//模拟SPI写数据
#if _CPOL==1&&_CPHA==1 //MODE 1 1
void Spi_Write_Byte(uint8_t dat)
{
uint8_t len;
SPI_CLK_HIGH;
for(len=0;len<8;len++)
{
SPI_CLK_LOW;//时钟上升沿输出数据
//nrf_delay_ms(1);
if(dat&0x80) //发送数据
{
SPI_MOSI_HIGH;
}
else
{
SPI_MOSI_LOW;
}
SPI_CLK_HIGH;
// nrf_delay_ms(1);
dat <<= 1;
}
}
//模拟SPI读数据
uint8_t Spi_Read_Byte(void)
{
uint8_t cnt;
uint8_t dat=0;
SPI_CLK_LOW;
for(cnt=0;cnt<8;cnt++)
{
SPI_CLK_LOW;
nrf_delay_ms(1);
dat <<= 1;
if(SPI_MISO_READ)
{
dat |= 0x01;
}
else
{
dat &= 0xfe;
}
SPI_CLK_HIGH;
nrf_delay_ms(1);
}
SPI_CLK_HIGH;
return dat;
}
#endif
模拟SPI的介绍请移步SPI专题(一)——基础知识
3.2 LCD驱动
//头文件
#ifndef __LCD128X64__H__
#define __LCD128X64__H__
#include "nrf51.h"
#include "nrf_gpio.h"
#define Max_Column 128
#define Max_Row 64
#define SIZE 16
#define LCD_CMD 0 //写命令
#define LCD_DATA 1 //写数据
#define LED_LIGHT1 28
#define LCD_CS 3
#define LCD_CS_LOW nrf_gpio_pin_clear(LCD_CS)
#define LCD_CS_HIGH nrf_gpio_pin_set(LCD_CS)
#define LCD_RST 4
#define LCD_RST_LOW nrf_gpio_pin_clear(LCD_RST)
#define LCD_RST_HIGH nrf_gpio_pin_set(LCD_RST)
#define LCD_A0 5
#define LCD_A0_LOW nrf_gpio_pin_clear(LCD_A0)
#define LCD_A0_HIGH nrf_gpio_pin_set(LCD_A0)
typedef enum
{
FONT_5X8 = 1,
FONT_8X16,
FONT_16X16,
FONT_128X64,
SHOW_POINT,
SHOW_LINE
}Typeface;
void GPIO_LCD_Init(void);
void LCD_Write_Byte(uint8_t dat,uint8_t cmd);
void LCD_Init(void);
void LCD_Set_Pos(uint8_t page,uint8_t column);
void LCD_Display_Clear(void);
void LCD_Draw_Point(uint8_t page,uint8_t column);
void LCD_Draw_Line_Y(uint8_t page,uint8_t column);
void LCD_All_Screen(void);
void Lcd_Display_OneChar(uint8_t page,uint8_t column,uint8_t str);
void Lcd_Display_String(uint8_t page,uint8_t column,uint8_t *str);
uint32_t Lcd_Pow(uint8_t m,uint8_t n);
void Lcd_Display_Num(uint8_t page,uint8_t column,uint32_t num,uint8_t len,uint8_t size_num);
void Display_128x64(uint8_t *dp);
void Display_Graphic_5x8(uint8_t page,uint8_t column,uint8_t *dp);
void Display_Graphic_8x16(uint8_t page,uint8_t column,uint8_t *dp);
void Display_Graphic_16x16(uint8_t page,uint8_t column,uint8_t *dp);
void DisplayFont(uint8_t page, uint8_t column, uint8_t No, uint8_t typeface);
void display_string_8x16(uint8_t page,uint8_t column,uint8_t *text);
void Lcd_Light(void);
void OLED_Init(void);
void LCD_Draw_Line_X(uint8_t page,uint8_t column);
#endif
//源文件,相关GPIO初始化
void GPIO_LCD_Init(void)
{
nrf_gpio_cfg_output(LCD_CS);
nrf_gpio_cfg_output(LCD_RST);
nrf_gpio_cfg_output(LCD_A0);
LCD_CS_HIGH;
}
MCU向LCD的写入数据:
//LCD写数据
void LCD_Write_Byte(uint8_t dat,uint8_t cmd)
{
// uint8_t len;
LCD_CS_LOW;
if(cmd)
LCD_A0_HIGH;
else
LCD_A0_LOW;
Spi_Write_Byte(dat);
LCD_CS_HIGH;
LCD_A0_HIGH;
}
//初始化
void LCD_Init(void)
{
LCD_RST_HIGH;
nrf_delay_ms(20);
LCD_RST_LOW;
nrf_delay_ms(20);
LCD_RST_HIGH;
LCD_Write_Byte(0xe2,LCD_CMD); //软复位
nrf_delay_ms(20);
LCD_Write_Byte(0xae,LCD_CMD);//显示关闭
LCD_Write_Byte(0x2c,LCD_CMD);// open VB circuit
nrf_delay_ms(20);
LCD_Write_Byte(0x2e,LCD_CMD);// open VR circuit
nrf_delay_ms(20);
LCD_Write_Byte(0x2f,LCD_CMD);//voltage follower ON regulator ON booster ON
nrf_delay_ms(20);
LCD_Write_Byte(0x22,LCD_CMD);
LCD_Write_Byte(0x81,LCD_CMD);
LCD_Write_Byte(0x3f,LCD_CMD);
LCD_Write_Byte(0xa2,LCD_CMD);//
LCD_Write_Byte(0xa0,LCD_CMD);
LCD_Write_Byte(0xc8,LCD_CMD);//com64 --> com1
LCD_Write_Byte(0x10,LCD_CMD);//Set Column Address 4 higher bits = 0
LCD_Write_Byte(0x00,LCD_CMD);//Set Column Address 4 lower bits = 1 , from IC SEG1 -> SEG128
LCD_Write_Byte(0xb0,LCD_CMD);//Set Page Address = 0
LCD_Write_Byte(0xa6,LCD_CMD);//Normal Display (not reverse dispplay)
LCD_Write_Byte(0xaf,LCD_CMD);//Display ON
/*
LCD_Write_Byte(0xa0,LCD_CMD); //列扫描顺序:从左到右
LCD_Write_Byte(0xc0,LCD_CMD); //行扫描顺序:C8:从下到上,c0:从上到下
LCD_Write_Byte(0xa2,LCD_CMD); //设置偏压比1/9
LCD_Write_Byte(0x2f,LCD_CMD); //控制电源
LCD_Write_Byte(0x25,LCD_CMD); //粗调对比度
LCD_Write_Byte(0x81,LCD_CMD); //微调对比度,进入微调对比度命令
LCD_Write_Byte(0x10,LCD_CMD); //设置电压的参数RR值
LCD_Write_Byte(0x40,LCD_CMD); //起始行:第一行开始
LCD_Write_Byte(0xaf,LCD_CMD); //开显示*/
LCD_Display_Clear();
LCD_Set_Pos(0,0);
}
//坐标设置
void LCD_Set_Pos(uint8_t page,uint8_t column)
{
LCD_Write_Byte(0xb0 + page,LCD_CMD); //64行分成8个页
LCD_Write_Byte(((column >> 4) & 0x0f) | 0x10,LCD_CMD); //列地址高四位
LCD_Write_Byte((column & 0x0f) | 0x01,LCD_CMD); //设置列地址的低四位
}
//清屏
/清屏
void LCD_Display_Clear(void)
{
uint8_t page,j;
for(page=0;page<8;page++)
{
LCD_Write_Byte(0xb0+page,LCD_CMD); //设置显示页地址,即横坐标
LCD_Write_Byte(0X00,LCD_CMD); //列地址的低 4 位
LCD_Write_Byte(0X10,LCD_CMD); //列地址的高 4 位
for(j=0;j<128;j++)
{
LCD_Write_Byte(0X00,LCD_DATA);//写入1则显示,写入0则清屏
}
}
}
//显示相关实现
/*显示128x64点阵图像*/
void Display_128x64(uint8_t *dp)
{
uint8_t i,j;
for(j=0;j<8;j++)
{
LCD_Set_Pos(j+1,1);
for (i=0;i<128;i++)
{
LCD_Write_Byte(*dp,LCD_DATA); /*写数据到LCD,每写完一个8位的数据后列地址自动加1*/
dp++;
}
}
}
//显示一个点
void LCD_Draw_Point(uint8_t page,uint8_t column)
{
LCD_Set_Pos(page, column);
LCD_Write_Byte(0x01,LCD_DATA);
}
//划线
void LCD_Draw_Line_Y(uint8_t page,uint8_t column)
{
uint8_t i,j;
for(i=page;i<8;i++)
{
LCD_Write_Byte(0xb0+i,LCD_CMD); //设置显示页地址,即横坐标
for(j=0;j<0x08;j++)
{
LCD_Write_Byte(0x00+j,LCD_CMD); //只加其中低或者高,相当于连续显示竖线
LCD_Write_Byte(0X10+j,LCD_CMD); //横纵坐标都同时递增,相当于竖线平移
LCD_Write_Byte(0Xff,LCD_DATA);//写入1则显示,写入0则清屏
}
}
}
//可构造斑马线
void LCD_Draw_Line_X(uint8_t page,uint8_t column)
{
uint8_t i,j;
LCD_Set_Pos(page, column);
for(j=0;j<8;j++)
{
LCD_Write_Byte(0xb0+j,LCD_CMD);
LCD_Write_Byte(0X00,LCD_CMD); //列地址的低 4 位
LCD_Write_Byte(0X10,LCD_CMD); //列地址的高 4 位
for(i=0;i<128;i++)
{
//LCD_Write_Byte(0x0f,LCD_DATA);
LCD_Write_Byte(0x01,LCD_DATA);
}
}
}
/显示单独一个字符
void Lcd_Display_OneChar(uint8_t page,uint8_t column,uint8_t str)
{
uint8_t i=0,ret=0;
//ret = str -32;
ret = str - ' ';//得到偏移后的值,对ASCLL码进行一个减法.即在二维数组里找它的位置
if(column>Max_Column-1)
{
column = 0;
page = page + 2;//针对16号的字符
}
if(SIZE == 16 )
{
LCD_Set_Pos(page,column);
//16的字体分成两部分写入
for(i=0;i<8;i++)
{
LCD_Write_Byte(F8X16[ret*16+i],LCD_DATA);
}
LCD_Set_Pos(page+1,column);//页地址增加1
for(i=0;i<8;i++)
{
LCD_Write_Byte(F8X16[ret*16+i+8],LCD_DATA);
}
}
else{
LCD_Set_Pos(page,column+1);
for(i=0;i<6;i++)
LCD_Write_Byte(F6x8[ret][i],LCD_DATA);
}
}
//字符串显示
void Lcd_Display_String(uint8_t page,uint8_t column,uint8_t *str)
{
uint8_t i=0;
while(str[i]!='\0')
{
Lcd_Display_OneChar(page,column,str[i]);
column += 8;
if(column>120)
{
column = 0;
page += 2;
}
i++;
}
}
//计算m的n次方
uint32_t Lcd_Pow(uint8_t m,uint8_t n)
{
uint32_t ret = 1;
while(n--)
{
ret *= m;
}
return ret;
}
//显示数字
//*num:显示的数字
void Lcd_Display_Num(uint8_t page,uint8_t column,uint32_t num,uint8_t len,uint8_t size_num)
{
uint8_t t,temp;
uint8_t flag = 0;
for(t=0;t<len;t++)
{
temp = (num / Lcd_Pow(10,len-t-1)) % 10;//把显示的数字一位一位取出来
if(((flag==0) && t) < (len-1))
{
if(temp == 0)
{
Lcd_Display_OneChar(page,column + (size_num/2) * t,'0');
continue;//temp=0时,结束本次循环,进入下次循环,但不跳出循环
}
else
{
flag = 1;
}
}
Lcd_Display_OneChar(page,column + (size_num/2) * t,temp + '0');
}
}
**注:**程序中涉及的字库可通过取自摸软件得到。