分享一个12864液晶屏的单片机驱动
https://www.toutiao.com/a6997216195019833892/
手上有个12864液晶屏,IC是ST7920的,内置中英文点阵字库。用于显示一些简短的字符串还是蛮合适的。周末花了点时间调了下这款点阵屏,用的是并口方式,使用了STM32F103的PB0-PB7作为液晶的DB0-DB7接口。实现了内建字库显示字符串的功能和用于图形显示的画点功能。对于图形显示来说,由于液晶屏画点需要进行读-改-写操作,导致刷屏很慢。所以加了个MCU内置图形缓存的功能(其实就是一个数组),对刷屏时间要求高的,可以先在MCU内置图形缓存中通过画点生成图形,然后一次性将全屏数据写入ST7920,这样刷屏速度有了非常明显的提高。
整个驱动比较简单,移植的话,就是修改下单片机的接口IO,修改下并口数据的写入读取函数就行了。
由于没花太多时间调试,驱动没经过严格测试,可能存在问题,但目前我所用到的功能还没出现过问题,对于正在调该款屏幕的人来说,程序的参考意义还是有的。
下面是显示效果图:
图形显示效果
内建字库字符串显示效果
代码如下:
#include "st7920.h"
/**
| RS | RW | description |
|——————|——————|———————————————————————————————————————————————————————|
| L | L | MPU write instruction to instruction register(IR) |
|——————|——————|———————————————————————————————————————————————————————|
| L | H | MPU read busy flag(BF) and address counter(AC) |
|——————|——————|———————————————————————————————————————————————————————|
| H | L | MPU write data to data register(DR) |
|——————|——————|———————————————————————————————————————————————————————|
| H | H | MPU read data from data register(DR) |
*/
/**
* @brief 液晶屏控制IO初始化
*/
void ST7920_IoInit(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/** 背光控制IO */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
/** RS信号 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/** RW信号 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/** EN信号 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/** PSB信号 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/** RST信号 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/** DB0-DB7信号 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
/**
* @brief 在ST7920 向DB0-DB7输出数据
*/
static inline void ST7920_SetBusData(uint8_t data)
{
GPIOB->ODR = (((uint16_t)GPIOB->ODR) & 0xFF00) | ((uint16_t)data);
}
/**
* @brief 读取ST7920 DB0-DB7上的数据
*/
static inline uint8_t ST7920_GetBusData(void)
{
return ((uint16_t)GPIOB->IDR) & 0x00FF;
}
/**
* @brief 等待ST7920的上条指令或数据处理完毕
*/
void ST7920_WaitBusy(void)
{
LCD_RS = 0;
LCD_RW = 1;
LCD_E = 1;
ST7920_SetBusData(0xFF);
while(ST7920_GetBusData() & 0x80);
LCD_E = 0;
}
/**
* @brief 获取当前Address counter(AC)
*/
uint8_t ST7920_GetAddrCounter(void)
{
uint8_t addr;
LCD_RS = 0;
LCD_RW = 1;
LCD_E = 1;
ST7920_SetBusData(0xFF);
while(ST7920_GetBusData() & 0x80);
addr = ST7920_GetBusData();
LCD_E = 0;
return addr;
}
/**
* @brief 写指令码至ST7920指令寄存器(IR)
*/
void ST7920_WriteCmd(uint8_t cmd)
{
ST7920_WaitBusy();
LCD_RS = 0;
LCD_RW = 0;
LCD_E = 1;
ST7920_SetBusData(cmd);
LCD_E = 0;
}
/**
* @brief 写数据至ST7920数据寄存器(DR)
*/
void ST7920_WriteData(uint8_t data)
{
ST7920_WaitBusy();
LCD_RS = 1;
LCD_RW = 0;
LCD_E = 1;
ST7920_SetBusData(data);
LCD_E = 0;
}
/**
* @brief 从ST7920数据寄存器(DR)读取数据
*/
uint8_t ST7920_ReadData(void)
{
uint8_t data;
ST7920_WaitBusy();
LCD_RS = 1;
LCD_RW = 1;
LCD_E = 1;
ST7920_SetBusData(0xFF);
data = ST7920_GetBusData();
LCD_E = 0;
return data;
}
/**
* @brief DDRAM清零(DDRAM用于显示内建字库字体)
*/
void ST7920_DdramClear(void)
{
ST7920_WriteCmd(ST7920_CMD_FUNCTION_BASIC_INS | ST7920_CMD_FUNCTION_8BIT_INTERFACE); //设置为基本指令
ST7920_WriteCmd(ST7920_CMD_CLEAR); //清屏
}
/**
* @brief GDRAM清零(GDRAM用于显示图形)
*/
void ST7920_GdramClear(void)
{
uint8_t i,j;
ST7920_WriteCmd(ST7920_CMD_FUNCTION_EXTENDED_INS | ST7920_CMD_FUNCTION_8BIT_INTERFACE | ST7920_CMD_FUNCTION_GRAPHIC_OFF); //关闭图形显示
/** 清零GDRAM */
for(i=0; i<64; i++) {
ST7920_WriteCmd(0x80 + i);
ST7920_WriteCmd(0x80);
for (j=0; j<=0x0F; j++) {
ST7920_WriteData(0x00);
ST7920_WriteData(0x00);
}
}
ST7920_WriteCmd(ST7920_CMD_FUNCTION_EXTENDED_INS | ST7920_CMD_FUNCTION_8BIT_INTERFACE | ST7920_CMD_FUNCTION_GRAPHIC_ON); //开启图形显示
}
/**
* @brief 通过ST7920内建字库显示字符串(GB码)
* @attention 使用DDRAM显示字符串时,如果字符串有ASCII字符与中文字符混合的情况,
* ASCII字符必须成对出现。这是由于DDRAM的每个显示地址内含2字节数据,
* 如果字符串中出现奇数个ASCII字符,将导致随后的中文字符数据与DDRAM
* 地址不对齐,从而ASCII字符后的中文字符将乱码。
* 12864屏幕将ST7920的第一行和第二行的后半部分作为屏幕的下半屏,因此
* 要在液晶的第三行显示内建字库,line应该填1,offset应该填8。
* @param[in] line: DDRAM中的行[1-4]
* @param[in] offset: 行中字符位置偏移(按中文字符占位算)
* @param[in] str: 要显示的字符串(GB编码)
* @retval None.
*/
void ST7920_ShowBuildInStr(uint8_t line, uint8_t offset, const char *str)
{
uint8_t i;
uint8_t addr;
ASSERT((line >= 1) && (line <= 4));
ASSERT(offset <= 15);
ST7920_WriteCmd(ST7920_CMD_FUNCTION_BASIC_INS | ST7920_CMD_FUNCTION_8BIT_INTERFACE); //恢复为基本指令
addr = 0x80 + (line - 1) * 0x10 + offset;
ST7920_WriteCmd(addr);
for (i = 0; str[i] != 0; i++) {
ST7920_WriteData(str[i]);
}
}
/**
* @brief 按屏幕显示效果上的行意义,通过ST7920内建字库显示字符串
* @note 由于12864屏幕的下半屏并非对应于ST7920 DDRAM中的第三行第四行。
* 而是对应于DDRAM第一行第二行的后半段。因此增加此函数,用于按照
* 屏幕显示效果上的行意义进行内建字库显示。
* @param[in] line: 实体屏幕逻辑意义上的行[1-4]
* @param[in] offset: 行中字符位置偏移(按中文字符占位算)
* @param[in] str: 要显示的字符串(GB编码)
* @retval None.
*/
void ST7920_ScreenShowBuildInStr(uint8_t line, uint8_t offset, const char *str)
{
char tmp_str[17];
snprintf(tmp_str, sizeof(tmp_str), "%s", str);
switch (line)
{
case 1: ST7920_ShowBuildInStr(1, offset, tmp_str); break;
case 2: ST7920_ShowBuildInStr(2, offset, tmp_str); break;
case 3: ST7920_ShowBuildInStr(1, offset + 8, tmp_str); break;
case 4: ST7920_ShowBuildInStr(2, offset + 8, tmp_str); break;
default: break;
}
}
/**
* @brief 画点函数
* @param[in] x,y: 坐标
* @param[in] color: 点颜色
* @retval None.
*/
void ST7920_SetPixel(uint16_t x, uint16_t y, uint16_t color)
{
uint8_t x_ram, y_ram;
uint8_t data_h, data_l;
uint8_t offset;
ASSERT(x < 128);
ASSERT(y < 64);
ST7920_WriteCmd(ST7920_CMD_FUNCTION_EXTENDED_INS | ST7920_CMD_FUNCTION_8BIT_INTERFACE | ST7920_CMD_FUNCTION_GRAPHIC_ON); //切换为拓展指令
/** 计算GDRAM坐标 */
x_ram = 0x80 | (x/16 + 8*(y/32));
y_ram = 0x80 | (y%32);
/** 读取现有点数据 */
ST7920_WriteCmd(y_ram);
ST7920_WriteCmd(x_ram);
ST7920_ReadData(); //Dummy Read
data_h = ST7920_ReadData();
data_l = ST7920_ReadData();
/** 计算新画点数据 */
offset = x % 16;
if (offset < 8) {
if (color) data_h |= 0x80 >> offset;
else data_h &= ~(0x80 >> offset);
} else {
if (color) data_l |= 0x80 >> (offset - 8);
else data_l &= ~(0x80 >> (offset - 8));
}
/** 写入新点数据 */
ST7920_WriteCmd(y_ram);
ST7920_WriteCmd(x_ram);
ST7920_WriteData(data_h);
ST7920_WriteData(data_l);
}
/**
* @brief 将整页数据写入ST7920
* @retval None.
*/
void ST7920_FlushPage(uint8_t data[64][16])
{
uint16_t i, j;
ST7920_WriteCmd(ST7920_CMD_FUNCTION_EXTENDED_INS | ST7920_CMD_FUNCTION_8BIT_INTERFACE | ST7920_CMD_FUNCTION_GRAPHIC_ON); //切换为拓展指令
/** 刷新上半屏 */
for (i = 0; i < 32; i++) {
ST7920_WriteCmd(0x80 + i);
ST7920_WriteCmd(0x80);
for (j = 0; j < 8; j++) {
ST7920_WriteData(data[i][2*j]);
ST7920_WriteData(data[i][2*j+1]);
}
}
/** 刷新下半屏 */
for (i = 32; i < 64; i++) {
ST7920_WriteCmd(0x80 + i - 32);
ST7920_WriteCmd(0x80 + 8);
for (j = 0; j < 8; j++) {
ST7920_WriteData(data[i][2*j]);
ST7920_WriteData(data[i][2*j+1]);
}
}
}
#ifdef USE_MCU_GRAM
/** @defgroup MCU内部GRAM相关函数
* @brief 由于ST7920_SetPixel函数需要执行读-改-写操作,刷屏非常耗时。通过在MCU内部建立GRAM可大幅提高刷屏速度。
* @attention 使用ST7920_SetMcuGramPixel画完点后,需要调用ST7920_FlushMcuGram更新ST7920内部GDRAM;
* @{
*/
static uint8_t s_MCU_GRAM[64][16];
/**
* @brief 在MCU内部显存画点
* @param[in] x,y: 坐标
* @param[in] color: 点颜色
* @retval None.
*/
void ST7920_SetMcuGramPixel(uint16_t x, uint16_t y, uint16_t color)
{
uint8_t index_1, index_2;
uint8_t offset;
/** 计算显存数组下标及位偏移 */
index_1 = y;
index_2 = x/8;
offset = x % 8;
if (color) s_MCU_GRAM[index_1][index_2] |= 0x80 >> offset;
else s_MCU_GRAM[index_1][index_2] &= ~(0x80 >> offset);
}
/**
* @brief 将MCU内部显存写入ST7920
* @retval None.
*/
void ST7920_FlushMcuGram(void)
{
ST7920_FlushPage(s_MCU_GRAM);
}
/**
* @}
*/
#endif
/**
* @brief ST7920初始化
*/
void ST7920_DisplayInit(void)
{
LCD_BL_ON; //开启背光
LCD_PSB = 1; //并行接口
LCD_RST = 0;
delay_ms(5);
LCD_RST = 1;
delay_ms(50);
/******************** 初始化配置 ********************/
ST7920_WriteCmd(ST7920_CMD_FUNCTION_8BIT_INTERFACE); //设置并行接口为8位
ST7920_WriteCmd(ST7920_CMD_FUNCTION_8BIT_INTERFACE); //设置并行接口为8位
ST7920_WriteCmd(ST7920_CMD_FUNCTION_BASIC_INS | ST7920_CMD_FUNCTION_8BIT_INTERFACE); //设置为基本指令
// ST7920_WriteCmd(ST7920_CMD_DISPLAY_DISPLAY_ON | ST7920_CMD_DISPLAY_CURSOR_ON | ST7920_CMD_DISPLAY_BLINK_ON); //整体显示,游标显示,游标闪烁
ST7920_WriteCmd(ST7920_CMD_DISPLAY_DISPLAY_ON); //开总显示
ST7920_WriteCmd(ST7920_CMD_ENTRY_CURSOR_MOVE_RIGHT); //光标右移
ST7920_DdramClear();
/******************** 开启图形显示 ********************/
ST7920_DdramClear();
ST7920_WriteCmd(ST7920_CMD_FUNCTION_EXTENDED_INS | ST7920_CMD_FUNCTION_8BIT_INTERFACE | ST7920_CMD_FUNCTION_GRAPHIC_OFF); //设置为扩展指令,关闭图形显示
ST7920_WriteCmd(ST7920_CMD_SCROLL_OR_RAM_EN_VERTICAL); //使能垂直滚动
ST7920_WriteCmd(ST7920_CMD_FUNCTION_EXTENDED_INS | ST7920_CMD_FUNCTION_8BIT_INTERFACE | ST7920_CMD_FUNCTION_GRAPHIC_ON); //开启图形显示
ST7920_WriteCmd(ST7920_CMD_FUNCTION_BASIC_INS | ST7920_CMD_FUNCTION_8BIT_INTERFACE); //恢复为基本指令
}
头文件:
#ifndef ST7920_H__
#define ST7920_H__
#include "common.h"
#define LCD_RS PBout(11)
#define LCD_RS_DATA PBout(11) = 1
#define LCD_RS_CMD PBout(11) = 0
#define LCD_RW PBout(10)
#define LCD_RW_READ PBout(10) = 1
#define LCD_RW_WRITE PBout(10) = 0
#define LCD_E PCout(5)
#define LCD_DB0 PBout(0)
#define LCD_DB1 PBout(1)
#define LCD_DB2 PBout(2)
#define LCD_DB3 PBout(3)
#define LCD_DB4 PBout(4)
#define LCD_DB5 PBout(5)
#define LCD_DB6 PBout(6)
#define LCD_DB7 PBout(7)
#define LCD_PSB PCout(4)
#define LCD_RST PBout(9)
#define LCD_BL_ON PAout(7) = 1
#define LCD_BL_OFF PAout(7) = 0
/** @defgroup ST7920命令宏(根据数据手册,有些同组命令需要和或操作配合使用)
* @{
*/
/** 基本指令(只需设置一次ST7920_CMD_FUNCTION_BASIC_INS) */
#define ST7920_CMD_CLEAR 0b00000001 //清屏
#define ST7920_CMD_HOME 0b00000010 //光标复位
#define ST7920_CMD_ENTRY_CURSOR_MOVE_RIGHT 0b00000110 //光标自动右移
#define ST7920_CMD_ENTRY_CURSOR_MOVE_LEFT 0b00000100 //光标自动左移
#define ST7920_CMD_ENTRY_DISPLAY_SHIFT_RIGHT 0b00000101 //显示右移
#define ST7920_CMD_ENTRY_DISPLAY_SHIFT_LEFT 0b00000111 //显示左移
#define ST7920_CMD_DISPLAY_DISPLAY_ON 0b00001100 //显示开启
#define ST7920_CMD_DISPLAY_CURSOR_ON 0b00001010 //显示光标
#define ST7920_CMD_DISPLAY_BLINK_ON 0b00001001 //光标闪烁
#define ST7920_CMD_CURSOR_MOVE_LEFT_BY_1 0b00010000 //AC=AC-1
#define ST7920_CMD_CURSOR_MOVE_RIGHT_BY_1 0b00010100 //AC=AC+1
#define ST7920_CMD_DISPLAY_MOVE_LEFT_BY_1 0b00011000 //显示左移1,光标跟随(AC=AC)
#define ST7920_CMD_DISPLAY_MOVE_RIGHT_BY_1 0b00011100 //显示右移1,光标跟随(AC=AC)
#define ST7920_CMD_FUNCTION_8BIT_INTERFACE 0b00110000 //8Bit总线
#define ST7920_CMD_FUNCTION_4BIT_INTERFACE 0b00100000 //4Bit总线
#define ST7920_CMD_FUNCTION_EXTENDED_INS 0b00100100 //拓展指令
#define ST7920_CMD_FUNCTION_BASIC_INS 0b00100000 //基本指令
#define ST7920_CMD_SET_CGRAM_ADDR 0b01000000 //设置CGRAM AC地址(需要确保拓展指令中SR位为0)
#define ST7920_CMD_SET_DDRAM_ADDR 0b10000000 //设置DDRAM AC地址
/** 拓展指令(只需设置一次ST7920_CMD_FUNCTION_EXTENDED_INS)*/
#define ST7920_CMD_STAND_BY 0b00000001 //进入stand by模式
#define ST7920_CMD_SCROLL_OR_RAM_EN_VERTICAL 0b00000011 //使能垂直滚动
#define ST7920_CMD_SCROLL_OR_RAM_EN_IRAM 0b00000010 //使能IRAM
#define ST7920_CMD_REVERSE_LINE1 0b00000100 //选择DDRAM 第1行反转显示
#define ST7920_CMD_REVERSE_LINE2 0b00000101 //选择DDRAM 第2行反转显示
#define ST7920_CMD_REVERSE_LINE3 0b00000110 //选择DDRAM 第3行反转显示
#define ST7920_CMD_REVERSE_LINE4 0b00000111 //选择DDRAM 第4行反转显示
#define ST7920_CMD_FUNCTION_GRAPHIC_OFF 0b00000010 //关闭图形显示
#define ST7920_CMD_FUNCTION_GRAPHIC_ON 0b00000010 //开启图形显示
#define ST7920_CMD_SET_IRAM_OR_SCROLL_SCROLL 0b01000000 //DB5-DB0为垂直滚动地址
#define ST7920_CMD_SET_IRAM_OR_SCROLL_IRAM 0b01000000 //DB3-DB0为ICON RAM地址
#define ST7920_CMD_SET_GRAPHIC_RAM_ADDR 0b10000000 //设置GDRAM地址至AC
/**
* @}
*/
#define USE_BASIC_INSTRUCTION ST7920_WriteCmd(ST7920_CMD_FUNCTION_BASIC_INS | ST7920_CMD_FUNCTION_8BIT_INTERFACE)
#define USE_EXTENDED_INSTRUCTION ST7920_WriteCmd(ST7920_CMD_FUNCTION_EXTENDED_INS | ST7920_CMD_FUNCTION_8BIT_INTERFACE | ST7920_CMD_FUNCTION_GRAPHIC_ON);
void ST7920_IoInit(void);
void ST7920_WaitBusy(void);
uint8_t ST7920_GetAddrCounter(void);
void ST7920_WriteCmd(uint8_t cmd);
void ST7920_WriteData(uint8_t data);
uint8_t ST7920_ReadData(void);
void ST7920_DdramClear(void);
void ST7920_GdramClear(void);
void ST7920_ShowBuildInStr(uint8_t line, uint8_t offset, const char *str);
void ST7920_ScreenShowBuildInStr(uint8_t line, uint8_t offset, const char *str);
void ST7920_SetPixel(uint16_t x, uint16_t y, uint16_t color);
void ST7920_FlushPage(uint8_t data[64][16]);
#ifdef USE_MCU_GRAM
void ST7920_SetMcuGramPixel(uint16_t x, uint16_t y, uint16_t color);
void ST7920_FlushMcuGram(void);
#endif
void ST7920_DisplayInit(void);
#endif
赞 (0)