#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; // 已实现 }