443 lines
18 KiB
C
443 lines
18 KiB
C
#include "lcd_tft_154.h"
|
||
|
||
#define FILL_ZERO 0 // 填充0
|
||
#define FILL_SPACE 1 // 填充空格
|
||
|
||
// 因为这类SPI的屏幕,每次更新显示时,需要先配置坐标区域、再写显存,
|
||
// 在显示字符时,如果是一个个点去写坐标写显存,会非常慢,
|
||
// 因此开辟一片缓冲区,先将需要显示的数据写进缓冲区,最后再批量写入显存。
|
||
// 用户可以根据实际情况去修改此处缓冲区的大小,
|
||
// 例如,用户需要显示32*32的汉字时,需要的大小为 32*32*2 = 2048 字节(每个像素点占2字节)
|
||
static uint16_t *pri; // 显示缓存
|
||
|
||
// 等待通讯完成
|
||
static void _wait_finish(spi_t *lcd_spi)
|
||
{
|
||
while ((lcd_spi->spi->Instance->SR & 0x0080) != RESET)
|
||
; // 等待通信完成
|
||
}
|
||
|
||
// 等待发送缓冲区清空
|
||
static void _wait_send_empty(spi_t *lcd_spi)
|
||
{
|
||
while ((lcd_spi->spi->Instance->SR & 0x0002) == 0)
|
||
; // 等待发送缓冲区清空
|
||
}
|
||
|
||
static void _write_command(spi_t *lcd_spi, uint8_t command)
|
||
{
|
||
_wait_finish(lcd_spi); // 等待通讯完成
|
||
|
||
lcd_spi->gpios.dc->reset(*lcd_spi->gpios.dc); // DC置低,表示发送命令
|
||
(lcd_spi->spi)->Instance->DR = command; // 发送数据
|
||
|
||
_wait_send_empty(lcd_spi); // 等待发送缓冲区清空
|
||
_wait_finish(lcd_spi); // 等待通讯完成 等待通信完成
|
||
|
||
lcd_spi->gpios.dc->set(*lcd_spi->gpios.dc); // DC置高,表示发送数据
|
||
}
|
||
|
||
static void _write_data_8bit(spi_t *lcd_spi, uint8_t data)
|
||
{
|
||
lcd_spi->spi->Instance->DR = data; // 发送数据
|
||
_wait_send_empty(lcd_spi); // 等待发送缓冲区清空
|
||
}
|
||
|
||
static void _write_data_16bit(spi_t *lcd_spi, uint16_t data)
|
||
{
|
||
lcd_spi->spi->Instance->DR = data >> 8; // 发送数据,高8位
|
||
_wait_send_empty(lcd_spi); // 等待发送缓冲区清空
|
||
lcd_spi->spi->Instance->DR = data; // 发送数据,低8位
|
||
_wait_send_empty(lcd_spi); // 等待发送缓冲区清空
|
||
}
|
||
|
||
static void _write_buff(lcd_t *lcd, uint16_t *data, uint16_t size)
|
||
{
|
||
uint32_t i;
|
||
lcd->info.spi->spi->Instance->CR1 &= 0xFFBF; // 关闭SPI
|
||
lcd->info.spi->spi->Instance->CR1 |= 0x0800; // 切换成16位数据格式
|
||
lcd->info.spi->spi->Instance->CR1 |= 0x0040; // 使能SPI
|
||
|
||
lcd->info.spi->gpios.cs->reset(*lcd->info.spi->gpios.cs);
|
||
|
||
for (i = 0; i < size; i++)
|
||
{
|
||
|
||
lcd->info.spi->spi->Instance->DR = data[i];
|
||
_wait_send_empty(lcd->info.spi); // 等待发送缓冲区清空
|
||
}
|
||
|
||
_wait_finish(lcd->info.spi); // 等待通讯完成
|
||
lcd->info.spi->gpios.cs->set(*lcd->info.spi->gpios.cs);
|
||
|
||
lcd->info.spi->spi->Instance->CR1 &= 0xFFBF; // 关闭SPI
|
||
lcd->info.spi->spi->Instance->CR1 &= 0xF7FF; // 切换成8位数据格式
|
||
lcd->info.spi->spi->Instance->CR1 |= 0x0040; // 使能SPI
|
||
}
|
||
|
||
// 设置方向
|
||
static void _set_dir(lcd_t *lcd, uint8_t scan_dir)
|
||
{
|
||
lcd->info.dir = scan_dir;
|
||
lcd->info.spi->gpios.cs->reset(*lcd->info.spi->gpios.cs);
|
||
|
||
if (scan_dir == Direction_H) // 横屏显示
|
||
{
|
||
_write_command(lcd->info.spi, 0x36); // 显存访问控制 指令,用于设置访问显存的方式
|
||
_write_data_8bit(lcd->info.spi, 0x70); // 横屏显示
|
||
lcd->info.x_offset = 0; // 设置控制器坐标偏移量
|
||
lcd->info.y_offset = 0;
|
||
}
|
||
else if (scan_dir == Direction_V)
|
||
{
|
||
_write_command(lcd->info.spi, 0x36); // 显存访问控制 指令,用于设置访问显存的方式
|
||
_write_data_8bit(lcd->info.spi, 0x00); // 垂直显示
|
||
lcd->info.x_offset = 0; // 设置控制器坐标偏移量
|
||
lcd->info.y_offset = 0;
|
||
}
|
||
else if (scan_dir == Direction_H_Flip)
|
||
{
|
||
_write_command(lcd->info.spi, 0x36); // 显存访问控制 指令,用于设置访问显存的方式
|
||
_write_data_8bit(lcd->info.spi, 0xA0); // 横屏显示,并上下翻转,RGB像素格式
|
||
lcd->info.x_offset = 80; // 设置控制器坐标偏移量
|
||
lcd->info.y_offset = 0;
|
||
}
|
||
else if (scan_dir == Direction_V_Flip)
|
||
{
|
||
_write_command(lcd->info.spi, 0x36); // 显存访问控制 指令,用于设置访问显存的方式
|
||
_write_data_8bit(lcd->info.spi, 0xC0); // 垂直显示 ,并上下翻转,RGB像素格式
|
||
lcd->info.x_offset = 0; // 设置控制器坐标偏移量
|
||
lcd->info.y_offset = 80;
|
||
}
|
||
_wait_finish(lcd->info.spi); // 等待通讯完成
|
||
lcd->info.spi->gpios.cs->set(*lcd->info.spi->gpios.cs);
|
||
}
|
||
|
||
// 设置画笔颜色
|
||
static void _set_color(lcd_t *lcd, uint32_t color)
|
||
{
|
||
uint16_t red_value = 0, green_value = 0, blue_value = 0; // 各个颜色通道的值
|
||
red_value = (uint16_t)((color & 0x00F80000) >> 8); // 转换成 16位 的RGB565颜色
|
||
green_value = (uint16_t)((color & 0x0000FC00) >> 5);
|
||
blue_value = (uint16_t)((color & 0x000000F8) >> 3);
|
||
lcd->info.color = (uint16_t)(red_value | green_value | blue_value); // 将颜色写入全局LCD参数
|
||
}
|
||
|
||
static void _set_address(lcd_t *lcd, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
|
||
{
|
||
lcd->info.spi->gpios.cs->reset(*lcd->info.spi->gpios.cs);
|
||
|
||
_write_command(lcd->info.spi, 0x2a); // 列地址设置,即X坐标
|
||
_write_data_16bit(lcd->info.spi, x1 + lcd->info.x_offset);
|
||
_write_data_16bit(lcd->info.spi, x2 + lcd->info.x_offset);
|
||
|
||
_write_command(lcd->info.spi, 0x2b); // 行地址设置,即Y坐标
|
||
_write_data_16bit(lcd->info.spi, y1 + lcd->info.y_offset);
|
||
_write_data_16bit(lcd->info.spi, y2 + lcd->info.y_offset);
|
||
|
||
_write_command(lcd->info.spi, 0x2c); // 开始写入显存,即要显示的颜色数据
|
||
|
||
_wait_finish(lcd->info.spi); // 等待通讯完成
|
||
lcd->info.spi->gpios.cs->set(*lcd->info.spi->gpios.cs);
|
||
}
|
||
|
||
static void _set_asscii_font(lcd_t *lcd, lcd_font *font)
|
||
{
|
||
lcd->info.ascii_font = font;
|
||
}
|
||
|
||
static void _set_hz_font(lcd_t *lcd, lcd_font *font)
|
||
{
|
||
lcd->info.hz_font = font;
|
||
switch (font->width)
|
||
{
|
||
case 12:
|
||
lcd->driver.set_asscii_font(lcd, &ascii_font_12);
|
||
break; // 设置ASCII字符的字体为 1206
|
||
case 16:
|
||
lcd->driver.set_asscii_font(lcd, &ascii_font_16);
|
||
break; // 设置ASCII字符的字体为 1608
|
||
case 20:
|
||
lcd->driver.set_asscii_font(lcd, &ascii_font_20);
|
||
break; // 设置ASCII字符的字体为 2010
|
||
case 24:
|
||
lcd->driver.set_asscii_font(lcd, &ascii_font_24);
|
||
break; // 设置ASCII字符的字体为 2412
|
||
case 32:
|
||
lcd->driver.set_asscii_font(lcd, &ascii_font_32);
|
||
break; // 设置ASCII字符的字体为 3216
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
/****************************************************************************************************************************************
|
||
* 函 数 名: LCD_DisplayChar
|
||
*
|
||
* 入口参数: x - 起始水平坐标
|
||
* y - 起始垂直坐标
|
||
* c - ASCII字符
|
||
*
|
||
* 函数功能: 在指定坐标显示指定的字符
|
||
*
|
||
* 说 明: 1. 可设置要显示的字体,例如使用 LCD_SetAsciiFont(&ASCII_Font24) 设置为 2412的ASCII字体
|
||
* 2. 可设置要显示的颜色,例如使用 LCD_SetColor(0xff0000FF) 设置为蓝色
|
||
* 3. 可设置对应的背景色,例如使用 LCD_SetBackColor(0x000000) 设置为黑色的背景色
|
||
* 4. 使用示例 LCD_DisplayChar( 10, 10, 'a') ,在坐标(10,10)显示字符 'a'
|
||
*
|
||
*****************************************************************************************************************************************/
|
||
|
||
void _display_char(lcd_t *lcd, uint16_t x, uint16_t y, uint8_t c)
|
||
{
|
||
uint16_t index = 0, counter = 0, i = 0, w = 0; // 计数变量
|
||
uint8_t disChar; // 存储字符的地址
|
||
|
||
c = c - 32; // 计算ASCII字符的偏移
|
||
|
||
lcd->info.spi->gpios.cs->reset(*lcd->info.spi->gpios.cs);
|
||
|
||
for (index = 0; index < lcd->info.ascii_font->sizes; index++)
|
||
{
|
||
disChar = lcd->info.ascii_font->table[c * lcd->info.ascii_font->sizes + index]; // 获取字符的模值
|
||
for (counter = 0; counter < 8; counter++)
|
||
{
|
||
if (disChar & 0x01)
|
||
{
|
||
pri[i] = lcd->info.color; // 当前模值不为0时,使用画笔色绘点
|
||
}
|
||
else
|
||
{
|
||
pri[i] = lcd->info.back_color; // 否则使用背景色绘制点
|
||
}
|
||
disChar >>= 1;
|
||
i++;
|
||
w++;
|
||
if (w == lcd->info.ascii_font->width) // 如果写入的数据达到了字符宽度,则退出当前循环
|
||
{ // 进入下一字符的写入的绘制
|
||
w = 0;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
lcd->driver.set_address(lcd, x, y, x + lcd->info.ascii_font->width - 1, y + lcd->info.ascii_font->height - 1); // 设置坐标
|
||
_write_buff(lcd, pri, lcd->info.ascii_font->width * lcd->info.ascii_font->height); // 写入显存
|
||
}
|
||
|
||
void _display_chinese(lcd_t *lcd, uint16_t x, uint16_t y, char *text)
|
||
{
|
||
uint16_t i = 0, index = 0, counter = 0; // 计数变量
|
||
uint16_t addr; // 字模地址
|
||
uint8_t disChar; // 字模的值
|
||
uint16_t Xaddress = 0; // 水平坐标
|
||
|
||
while (1)
|
||
{
|
||
// 对比数组中的汉字编码,用以定位该汉字字模的地址
|
||
if (*(lcd->info.hz_font->table + (i + 1) * lcd->info.hz_font->sizes + 0) == *text && *(lcd->info.hz_font->table + (i + 1) * lcd->info.hz_font->sizes + 1) == *(text + 1))
|
||
{
|
||
addr = i; // 字模地址偏移
|
||
break;
|
||
}
|
||
i += 2; // 每个中文字符编码占两字节
|
||
|
||
if (i >= lcd->info.hz_font->table_rows)
|
||
break; // 字模列表中无相应的汉字
|
||
}
|
||
i = 0;
|
||
for (index = 0; index < lcd->info.hz_font->sizes; index++)
|
||
{
|
||
disChar = *(lcd->info.hz_font->table + (addr)*lcd->info.hz_font->sizes + index); // 获取相应的字模地址
|
||
|
||
for (counter = 0; counter < 8; counter++)
|
||
{
|
||
if (disChar & 0x01)
|
||
{
|
||
pri[i] = lcd->info.color; // 当前模值不为0时,使用画笔色绘点
|
||
}
|
||
else
|
||
{
|
||
pri[i] = lcd->info.back_color; // 否则使用背景色绘制点
|
||
}
|
||
i++;
|
||
disChar >>= 1;
|
||
Xaddress++; // 水平坐标自加
|
||
|
||
if (Xaddress == lcd->info.hz_font->width) // 如果水平坐标达到了字符宽度,则退出当前循环
|
||
{ // 进入下一行的绘制
|
||
Xaddress = 0;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
lcd->driver.set_address(lcd, x, y, x + lcd->info.hz_font->width - 1, y + lcd->info.hz_font->height - 1); // 设置坐标
|
||
_write_buff(lcd, pri, lcd->info.hz_font->width * lcd->info.hz_font->height); // 写入显存
|
||
}
|
||
|
||
static void _display_text(lcd_t *lcd, uint16_t x, uint16_t y, char *text)
|
||
{
|
||
while (*text != 0) // 判断是否为空字符
|
||
{
|
||
if (*text <= 0x7F) // 判断是否为ASCII码
|
||
{
|
||
_display_char(lcd, x, y, *text); // 显示ASCII
|
||
x += lcd->info.ascii_font->width; // 水平坐标调到下一个字符处
|
||
text++; // 字符串地址+1
|
||
}
|
||
else // 若字符为汉字
|
||
{
|
||
_display_chinese(lcd, x, y, text); // 显示汉字
|
||
x += lcd->info.hz_font->width; // 水平坐标调到下一个字符处
|
||
text += 2; // 字符串地址+2,汉字的编码要2字节
|
||
}
|
||
}
|
||
}
|
||
|
||
// 设置背景色
|
||
static void _full_fill(lcd_t *lcd, uint32_t color)
|
||
{
|
||
uint16_t red_value = 0, green_value = 0, blue_value = 0; // 各个颜色通道的值
|
||
red_value = (uint16_t)((color & 0x00F80000) >> 8); // 转换成 16位 的RGB565颜色
|
||
green_value = (uint16_t)((color & 0x0000FC00) >> 5);
|
||
blue_value = (uint16_t)((color & 0x000000F8) >> 3);
|
||
|
||
lcd->info.back_color = (uint16_t)(red_value | green_value | blue_value); // 将颜色写入全局LCD参数
|
||
}
|
||
|
||
static void _clear(lcd_t *lcd)
|
||
{
|
||
uint32_t i;
|
||
lcd->driver.set_address(lcd, 0, 0, lcd->info.width - 1, lcd->info.height - 1);
|
||
lcd->info.spi->spi->Instance->CR1 &= 0xFFBF; // 关闭SPI
|
||
lcd->info.spi->spi->Instance->CR1 |= 0x0800; // 切换成16位数据格式
|
||
lcd->info.spi->spi->Instance->CR1 |= 0x0040; // 使能SPI
|
||
|
||
lcd->info.spi->gpios.cs->reset(*lcd->info.spi->gpios.cs);
|
||
|
||
for (i = 0; i < lcd->info.width * lcd->info.height; i++)
|
||
{
|
||
|
||
lcd->info.spi->spi->Instance->DR = lcd->info.back_color;
|
||
_wait_send_empty(lcd->info.spi); // 等待发送缓冲区清空
|
||
}
|
||
|
||
_wait_finish(lcd->info.spi); // 等待通讯完成
|
||
lcd->info.spi->gpios.cs->set(*lcd->info.spi->gpios.cs);
|
||
|
||
lcd->info.spi->spi->Instance->CR1 &= 0xFFBF; // 关闭SPI
|
||
lcd->info.spi->spi->Instance->CR1 &= 0xF7FF; // 切换成8位数据格式
|
||
lcd->info.spi->spi->Instance->CR1 |= 0x0040; // 使能SPI
|
||
}
|
||
|
||
static int32_t _init(lcd_t *lcd)
|
||
{
|
||
HAL_Delay(10);
|
||
pri = (uint16_t *)osel_mem_alloc2(1024 * sizeof(lcd));
|
||
lcd->info.pri = (void *)pri;
|
||
lcd->info.spi->gpios.cs->reset(*lcd->info.spi->gpios.cs);
|
||
_write_command(lcd->info.spi, 0x36); // 显存访问控制 指令,用于设置访问显存的方式
|
||
_write_data_8bit(lcd->info.spi, 0x00); // 配置成 从上到下、从左到右,RGB像素格式
|
||
_write_command(lcd->info.spi, 0x3A); // 接口像素格式 指令,用于设置使用 12位、16位还是18位色
|
||
_write_data_8bit(lcd->info.spi, 0x05); // 此处配置成 16位 像素格式
|
||
|
||
// 接下来很多都是电压设置指令,直接使用厂家给设定值
|
||
_write_command(lcd->info.spi, 0xB2);
|
||
_write_data_8bit(lcd->info.spi, 0x0C);
|
||
_write_data_8bit(lcd->info.spi, 0x0C);
|
||
_write_data_8bit(lcd->info.spi, 0x00);
|
||
_write_data_8bit(lcd->info.spi, 0x33);
|
||
_write_data_8bit(lcd->info.spi, 0x33);
|
||
|
||
_write_command(lcd->info.spi, 0xB7); // 栅极电压设置指令
|
||
_write_data_8bit(lcd->info.spi, 0x35); // VGH = 13.26V,VGL = -10.43V
|
||
|
||
_write_command(lcd->info.spi, 0xBB); // 公共电压设置指令
|
||
_write_data_8bit(lcd->info.spi, 0x19); // VCOM = 1.35V
|
||
|
||
_write_command(lcd->info.spi, 0xC0);
|
||
_write_data_8bit(lcd->info.spi, 0x2C);
|
||
|
||
_write_command(lcd->info.spi, 0xC2); // VDV 和 VRH 来源设置
|
||
_write_data_8bit(lcd->info.spi, 0x01); // VDV 和 VRH 由用户自由配置
|
||
|
||
_write_command(lcd->info.spi, 0xC3); // VRH电压 设置指令
|
||
_write_data_8bit(lcd->info.spi, 0x12); // VRH电压 = 4.6+( vcom+vcom offset+vdv)
|
||
|
||
_write_command(lcd->info.spi, 0xC4); // VDV电压 设置指令
|
||
_write_data_8bit(lcd->info.spi, 0x20); // VDV电压 = 0v
|
||
|
||
_write_command(lcd->info.spi, 0xC6); // 正常模式的帧率控制指令
|
||
_write_data_8bit(lcd->info.spi, 0x0F); // 设置屏幕控制器的刷新帧率为60帧
|
||
|
||
_write_command(lcd->info.spi, 0xD0); // 电源控制指令
|
||
_write_data_8bit(lcd->info.spi, 0xA4); // 无效数据,固定写入0xA4
|
||
_write_data_8bit(lcd->info.spi, 0xA1); // AVDD = 6.8V ,AVDD = -4.8V ,VDS = 2.3V
|
||
|
||
_write_command(lcd->info.spi, 0xE0); // 正极电压伽马值设定
|
||
_write_data_8bit(lcd->info.spi, 0xD0);
|
||
_write_data_8bit(lcd->info.spi, 0x04);
|
||
_write_data_8bit(lcd->info.spi, 0x0D);
|
||
_write_data_8bit(lcd->info.spi, 0x11);
|
||
_write_data_8bit(lcd->info.spi, 0x13);
|
||
_write_data_8bit(lcd->info.spi, 0x2B);
|
||
_write_data_8bit(lcd->info.spi, 0x3F);
|
||
_write_data_8bit(lcd->info.spi, 0x54);
|
||
_write_data_8bit(lcd->info.spi, 0x4C);
|
||
_write_data_8bit(lcd->info.spi, 0x18);
|
||
_write_data_8bit(lcd->info.spi, 0x0D);
|
||
_write_data_8bit(lcd->info.spi, 0x0B);
|
||
_write_data_8bit(lcd->info.spi, 0x1F);
|
||
_write_data_8bit(lcd->info.spi, 0x23);
|
||
|
||
_write_command(lcd->info.spi, 0xE1); // 负极电压伽马值设定
|
||
_write_data_8bit(lcd->info.spi, 0xD0);
|
||
_write_data_8bit(lcd->info.spi, 0x04);
|
||
_write_data_8bit(lcd->info.spi, 0x0C);
|
||
_write_data_8bit(lcd->info.spi, 0x11);
|
||
_write_data_8bit(lcd->info.spi, 0x13);
|
||
_write_data_8bit(lcd->info.spi, 0x2C);
|
||
_write_data_8bit(lcd->info.spi, 0x3F);
|
||
_write_data_8bit(lcd->info.spi, 0x44);
|
||
_write_data_8bit(lcd->info.spi, 0x51);
|
||
_write_data_8bit(lcd->info.spi, 0x2F);
|
||
_write_data_8bit(lcd->info.spi, 0x1F);
|
||
_write_data_8bit(lcd->info.spi, 0x1F);
|
||
_write_data_8bit(lcd->info.spi, 0x20);
|
||
_write_data_8bit(lcd->info.spi, 0x23);
|
||
|
||
_write_command(lcd->info.spi, 0x21); // 打开反显,因为面板是常黑型,操作需要反过来
|
||
|
||
// 退出休眠指令,LCD控制器在刚上电、复位时,会自动进入休眠模式 ,因此操作屏幕之前,需要退出休眠
|
||
_write_command(lcd->info.spi, 0x11); // 退出休眠 指令
|
||
HAL_Delay(120); // 需要等待120ms,让电源电压和时钟电路稳定下来
|
||
// 打开显示指令,LCD控制器在刚上电、复位时,会自动关闭显示
|
||
_write_command(lcd->info.spi, 0x29); // 打开显示
|
||
|
||
_wait_finish(lcd->info.spi); // 等待通讯完成
|
||
lcd->info.spi->gpios.cs->set(*lcd->info.spi->gpios.cs);
|
||
|
||
// 以下进行一些驱动的默认设置
|
||
lcd->driver.set_dir(lcd, Direction_V); // 默认竖屏显示
|
||
lcd->driver.full_fill(lcd, LCD_BLACK); // 默认背景色为黑色
|
||
lcd->driver.set_color(lcd, LCD_WHITE); // 默认画笔颜色为白色
|
||
lcd->driver.clear(lcd); // 清除屏幕
|
||
lcd->driver.set_asscii_font(lcd, &ascii_font_24); // 默认ASCII字体
|
||
lcd->info.show_num_mode = FILL_ZERO; // 设置变量显示模式,多余位填充空格还是填充0
|
||
|
||
return 0;
|
||
}
|
||
|
||
void lcd_tft_154_init(lcd_driver_t *driver)
|
||
{
|
||
DBG_ASSERT(driver != NULL __DBG_LINE);
|
||
|
||
driver->init = _init; // 已实现
|
||
driver->set_dir = _set_dir; // 已实现
|
||
driver->set_color = _set_color; // 已实现
|
||
driver->set_address = _set_address; // 已实现
|
||
driver->set_asscii_font = _set_asscii_font; // 已实现
|
||
driver->set_hz_font = _set_hz_font; // 已实现
|
||
driver->display_text = _display_text; // 已实现
|
||
driver->clear = _clear; // 已实现
|
||
driver->full_fill = _full_fill; // 已实现
|
||
}
|