一、概述
- 開發板:STM32F103C8T6
- 顯示器:ST7735S
- RT-Thread:5.0.0
玩過 GUI 的小夥伴都知道,介面的顯示是一個個畫素點組合起來的,那麼直接構建出來炫酷的 GUI 還是相對比較困難的,所以我們一般都會使用一些 GUI 庫來實現,比如 LVGL、QT、UGUI等,這樣對於驅動開發的人員來說就相對比較簡單了,
圖形庫應用的核心思想只需要提供一幀的緩衝區,我們只需要不斷的將緩衝區的資料寫入到 LCD 中即可,而緩衝區的內容由圖形庫實現,需要注意的是這個緩衝的建立方式,有的圖形庫會自己建立緩衝區,我們只需要負責重新整理 LCD 的顯示即可,而有的圖形庫是由驅動提供緩衝區,圖形庫負責寫入。
二、RT-Thread 移植
移植 RT-Thread 不是此文章的重點,可以參考一下我之前的筆記,或者直接使用 RT-Thread Studio、STM32CubeMX等工具直接生成,這裡我就不過多介紹了
三、LCD 驅動
使用過 RT-Thread 的小夥伴,都知道 RT-Thread 目前還不能直接使用工具生成我們想要的 LCD 驅動,所以這裡我們只能根據標準的驅動進行編寫了
-
驅動函式結構體
/* 驅動函式實現的結構體 */ #ifdef RT_USING_DEVICE_OPS const static struct rt_device_ops lcd_ops = { drv_lcd_init, RT_NULL, RT_NULL, RT_NULL, RT_NULL, drv_lcd_control }; #endif
-
註冊 LCD 裝置
int drv_lcd_hw_init(void) { rt_err_t result = RT_EOK; rt_uint32_t lcd_buff_size = lcd_buff_size = LCD_HEIGHT * LCD_WIDTH * 2; /* 建立LCD裝置物件 */ struct rt_device *device = &_lcd.lcd_dev; memset(&_lcd, 0x00, sizeof(_lcd)); LOG_D("drv_lcd_hw_init!\n"); /* 初始化lcd_lock訊號量 */ result = rt_sem_init(&_lcd.lcd_lock, "lcd_lock", 0, RT_IPC_FLAG_FIFO); if (result != RT_EOK) { LOG_E("init semaphore failed!\n"); result = -RT_ENOMEM; goto __exit; } /* 設定 LCD 裝置資訊 */ _lcd.lcd_info.height = LCD_HEIGHT; _lcd.lcd_info.width = LCD_WIDTH; _lcd.lcd_info.bits_per_pixel = LCD_BITS_PER_PIXEL; _lcd.lcd_info.pixel_format = RTGRAPHIC_PIXEL_FORMAT_RGB565; // 影像的格式(RGB:565) /* LCD 顯示緩衝區,大小為顯示一幀影像所需空間 */ _lcd.lcd_info.smem_len = lcd_buff_size; _lcd.lcd_info.framebuffer = rt_malloc(lcd_buff_size); if (_lcd.lcd_info.framebuffer == RT_NULL) { LOG_E("init frame buffer failed!\n"); result = -RT_ENOMEM; goto __exit; } /* 將緩衝區初始化為 0xFF */ memset(_lcd.lcd_info.framebuffer, 0xFF, lcd_buff_size); #ifdef RT_USING_DEVICE_OPS device->ops = &lcd_ops; #else device->init = drv_lcd_init; device->control = drv_lcd_control; #endif /* 註冊 LCD 裝置 */ rt_device_register(device, "lcd", RT_DEVICE_FLAG_RDWR); __exit: if (result != RT_EOK) { rt_sem_detach(&_lcd.lcd_lock); if (_lcd.lcd_info.framebuffer) { rt_free(_lcd.lcd_info.framebuffer); } } return result; }
-
LCD 控制函式的實現
static rt_err_t drv_lcd_control(struct rt_device *device, int cmd, void *args) { // struct drv_lcd_device *lcd = LCD_DEVICE(device); LOG_D("drv_lcd_control cmd is: %d\n", cmd); switch (cmd) { case RTGRAPHIC_CTRL_RECT_UPDATE: { rt_sem_take(&_lcd.lcd_lock, RT_TICK_PER_SECOND / 20); /* 重新整理緩衝區 */ rt_sem_release(&_lcd.lcd_lock); } break; case RTGRAPHIC_CTRL_POWERON: { /* LCD 退出睡眠模式 */ } break; case RTGRAPHIC_CTRL_POWEROFF: { /* LCD 進入睡眠模式 */ } break; case RTGRAPHIC_CTRL_GET_INFO: { /* 獲取 LCD 引數 */ memcpy(args, &_lcd.lcd_info, sizeof(_lcd.lcd_info)); } break; default: return -RT_EINVAL; } return RT_EOK; }
-
LCD 驅動功能實現
剩下的就比較簡單了,只需要參考 LCD 提供的案例程式進行更改就好了,主要有實現如下- drv_lcd_init: 完成 LCD 的復位、初始化、首次清屏工作
- drv_lcd_control: 完成 LCD 顯示區域的重新整理、螢幕引數的返回、亮屏和息屏等工作
注意:具體實現參考後面的程式原始碼,相對比較簡單,這裡就不過多介紹了
四、UGUI 介紹
-
介紹
µGUI 是一個用於嵌入式系統的免費開源圖形庫。 它獨立於平臺,可以輕鬆移植到幾乎任何微控制器系統。 只要顯示器能夠顯示圖形,μGUI 就不受某種顯示技術的限制。 因此,支援LCD、TFT、E-Paper、LED或OLED等顯示技術。 整個模組由兩個檔案組成:ugui.c 和 ugui.h。
注意:這裡的介紹我直接引用了作者的描述 -
獲取 UGUI
github:https://github.com/xidongxu/ugui -
檔案目錄
-
使用介紹
- 移植: 我們主要實現 ugui_port.c,這裡下載時已經提供了案例,所以我只需要在其中進行簡單的修改
- 使用: 使用相對比較簡單,直接參考 “µGUI v0.3.pdf” 文件即可,直接沒有難度
五、UGUI 移植
-
初始化
直接在 ugui_port.c 檔案中使用INIT_COMPONENT_EXPORT(ugui_port_init)
進行自動初始化,如下圖所示:
-
lcd_open 函式
這裡不要做任何更改,從函式中可以看出 LCD 相關的引數獲取,如下圖所示:
-
lcd_draw_pixel 函式
主要功能是在緩衝區中寫入一個畫素點的顏色,如下圖所示:
-
ugui_port_thread_entry 函式
這是執行緒的入口函式,主要目的是定期將緩衝區的資料寫入到 LCD 中,下圖所示:
注意:從以上步奏可以看出,移植 UGUI 時不需要更改任何引數,只需要在初始化時呼叫 ugui_port_init
函式即可。
六、程式原始碼
drv_lcd_st7735s.h
/**
* @file drv_lcd_st7735s.h
*
*/
#ifndef __DRV_LCD_ST7735S_H__
#define __DRV_LCD_ST7735S_H__
#include <rtthread.h>
#define LCD_HEIGHT 20 // LCD 高畫素
#define LCD_WIDTH 128 // LCD 寬畫素
#define LCD_BITS_PER_PIXEL 16 // 畫素點的資料寬度
#define LCD_CS_PIN_TYPE GPIOA // CS 引腳所在的組
#define LCD_CS_PIN GPIO_PIN_4 // 引腳編號
#define LCD_BCK_PIN GET_PIN(B, 1) // 背光引腳
#define LCD_DC_PIN GET_PIN(B, 8) // 資料引腳
#define LCD_RES_PIN GET_PIN(B, 9) // 復位引腳
#define WHITE 0xFFFF
#define BLACK 0x0000
#define BLUE 0x001F
#define BRED 0XF81F
#define GRED 0XFFE0
#define GBLUE 0X07FF
#define RED 0xF800
#define MAGENTA 0xF81F
#define GREEN 0x07E0
#define CYAN 0x7FFF
#define YELLOW 0xFFE0
#define BROWN 0XBC40
#define BRRED 0XFC07
#define GRAY 0X8430
#define GRAY175 0XAD75
#define GRAY151 0X94B2
#define GRAY187 0XBDD7
#define GRAY240 0XF79E
#endif /* __DRV_LCD_ST7735S_H__ */
drv_lcd_st7735s.c
/***************************************************************
檔名 : drv_lcd_st7735s.c
作者 : jiaozhu
版本 : V1.0
描述 : st7735s 顯示驅動
其他 : 無
日誌 : 初版 V1.0 2023/04/28
***************************************************************/
#include <board.h>
#include <rtthread.h>
#ifdef BSP_USING_LCD
#include "drv_spi.h"
#include <string.h>
#include "drv_lcd_st7735s.h"
//#define DRV_DEBUG
#define LOG_TAG "drv.lcd"
#include <drv_log.h>
static struct rt_spi_device *spi_dev_lcd;
struct drv_lcd_device
{
struct rt_device lcd_dev;
struct rt_device_graphic_info lcd_info;
struct rt_semaphore lcd_lock;
};
struct drv_lcd_device _lcd;
/**
* @brief LCD 命令寫入,寫入時資料引腳為低電平
*
* @param cmd 命令
* @retval 返回執行結果
*/
static rt_err_t lcd_write_cmd(const rt_uint8_t cmd)
{
rt_size_t len;
rt_pin_write(LCD_DC_PIN, PIN_LOW);
len = rt_spi_send(spi_dev_lcd, &cmd, 1);
if (len != 1)
{
LOG_I("lcd_write_cmd error. %d", len);
return -RT_ERROR;
}
else
{
return RT_EOK;
}
}
/**
* @brief LCD 資料寫入,寫入時資料引腳為高電平
*
* @param cmd 命令
* @retval 返回執行結果
*/
static rt_err_t lcd_write_data(const rt_uint8_t data)
{
rt_size_t len;
rt_pin_write(LCD_DC_PIN, PIN_HIGH);
len = rt_spi_send(spi_dev_lcd, &data, 1);
if (len != 1)
{
LOG_I("lcd_write_data error. %d", len);
return -RT_ERROR;
}
else
{
return RT_EOK;
}
}
/**
* @brief LCD 板級初始化
*
* @param None
* @retval int 初始化結果
*/
static int lcd_dev_init(void)
{
lcd_write_cmd(0x11); //Sleep out
rt_thread_delay(12); //Delay 12ms
//------------------------------------ST7735S Frame Rate-----------------------------------------//
lcd_write_cmd(0xB1);
lcd_write_data(0x05);
lcd_write_data(0x3C);
lcd_write_data(0x3C);
lcd_write_cmd(0xB2);
lcd_write_data(0x05);
lcd_write_data(0x3C);
lcd_write_data(0x3C);
lcd_write_cmd(0xB3);
lcd_write_data(0x05);
lcd_write_data(0x3C);
lcd_write_data(0x3C);
lcd_write_data(0x05);
lcd_write_data(0x3C);
lcd_write_data(0x3C);
//------------------------------------End ST7735S Frame Rate-----------------------------------------//
lcd_write_cmd(0xB4); //Dot inversion
lcd_write_data(0x03);
lcd_write_cmd(0xC0);
lcd_write_data(0x28);
lcd_write_data(0x08);
lcd_write_data(0x04);
lcd_write_cmd(0xC1);
lcd_write_data(0XC0);
lcd_write_cmd(0xC2);
lcd_write_data(0x0D);
lcd_write_data(0x00);
lcd_write_cmd(0xC3);
lcd_write_data(0x8D);
lcd_write_data(0x2A);
lcd_write_cmd(0xC4);
lcd_write_data(0x8D);
lcd_write_data(0xEE);
//---------------------------------End ST7735S Power Sequence-------------------------------------//
lcd_write_cmd(0xC5); //VCOM
lcd_write_data(0x1A);
lcd_write_cmd(0x36); //MX, MY, RGB mode
lcd_write_data(0xC0);
//------------------------------------ST7735S Gamma Sequence-----------------------------------------//
lcd_write_cmd(0xE0);
lcd_write_data(0x04);
lcd_write_data(0x22);
lcd_write_data(0x07);
lcd_write_data(0x0A);
lcd_write_data(0x2E);
lcd_write_data(0x30);
lcd_write_data(0x25);
lcd_write_data(0x2A);
lcd_write_data(0x28);
lcd_write_data(0x26);
lcd_write_data(0x2E);
lcd_write_data(0x3A);
lcd_write_data(0x00);
lcd_write_data(0x01);
lcd_write_data(0x03);
lcd_write_data(0x13);
lcd_write_cmd(0xE1);
lcd_write_data(0x04);
lcd_write_data(0x16);
lcd_write_data(0x06);
lcd_write_data(0x0D);
lcd_write_data(0x2D);
lcd_write_data(0x26);
lcd_write_data(0x23);
lcd_write_data(0x27);
lcd_write_data(0x27);
lcd_write_data(0x25);
lcd_write_data(0x2D);
lcd_write_data(0x3B);
lcd_write_data(0x00);
lcd_write_data(0x01);
lcd_write_data(0x04);
lcd_write_data(0x13);
//------------------------------------End ST7735S Gamma Sequence-----------------------------------------//
lcd_write_cmd(0x3A); //65k mode
lcd_write_data(0x05);
lcd_write_cmd(0x29); //Display on
return RT_EOK;
}
/**
* @brief 初始化 LCD 所需的引腳,並透過引腳復位 LCD
*
* @param None
* @retval None
*/
static void lcd_gpio_init(void)
{
/* 配置引腳模式 */
rt_pin_mode(LCD_DC_PIN, PIN_MODE_OUTPUT);
rt_pin_mode(LCD_RES_PIN, PIN_MODE_OUTPUT);
rt_pin_mode(LCD_BCK_PIN, PIN_MODE_OUTPUT);
/* 透過引腳復位 LCD */
rt_pin_write(LCD_BCK_PIN, PIN_LOW);
rt_pin_write(LCD_RES_PIN, PIN_LOW);
rt_thread_mdelay(12);
rt_pin_write(LCD_RES_PIN, PIN_HIGH);
/* 復位後延時一段時間,確保螢幕正常工作 */
rt_thread_mdelay(12);
}
/**
* @brief 初始化 LCD 所需的 SPI 外設
*
* @param None
* @retval int 操作結果
*/
static int lcd_spi_init(void)
{
/* 配置 SPI 埠,並指定 CS 引腳為 PA4 */
__HAL_RCC_GPIOA_CLK_ENABLE();
rt_hw_spi_device_attach("spi1", "spi10", LCD_CS_PIN_TYPE, LCD_CS_PIN);
/* 查詢裝置 */
spi_dev_lcd = (struct rt_spi_device *)rt_device_find("spi10");
if(RT_NULL == spi_dev_lcd)
{
LOG_E("Unable to find SPI device required for LCD");
return RT_ERROR;
}
/* 配置 SPI */
struct rt_spi_configuration cfg;
cfg.data_width = 8;
cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB;
cfg.max_hz = 42 * 1000 * 1000; /* 42M,SPI max 42MHz,lcd 4-wire spi */
spi_dev_lcd->bus ->owner = spi_dev_lcd;
rt_spi_configure(spi_dev_lcd, &cfg);
return RT_EOK;
}
/**
* @brief 設定需要繪圖的區域
*
* @param x1 start of x position
* @param y1 start of y position
* @param x2 end of x position
* @param y2 end of y position
* @retval None
*/
static void lcd_draw_area_set(rt_uint16_t x1, rt_uint16_t y1, rt_uint16_t x2, rt_uint16_t y2)
{
lcd_write_cmd(0x2a);
lcd_write_data(x1 >> 8);
lcd_write_data(x1);
lcd_write_data(x2 >> 8);
lcd_write_data(x2);
lcd_write_cmd(0x2b);
lcd_write_data(y1 >> 8);
lcd_write_data(y1);
lcd_write_data(y2 >> 8);
lcd_write_data(y2);
lcd_write_cmd(0x2C);
}
/**
* @brief LCD 清屏,將整個螢幕設定為指定顏色
*
* @param color 清空的顏色
* @retval None
*/
static void lcd_clear_screen(rt_uint16_t color)
{
rt_uint16_t i, j;
rt_uint8_t data[2] = {0};
data[0] = (color >> 8) & 0xFF;
data[1] = color & 0xFF;
/* 設定整個螢幕區域 */
lcd_draw_area_set(0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1);
/* 這裡直接透過 SPI 傳送資料,所以需要單獨將資料引腳拉高 */
rt_pin_write(LCD_DC_PIN, PIN_HIGH);
if (_lcd.lcd_info.framebuffer != RT_NULL)
{
/* 重置緩衝區 */
// memset(_lcd.lcd_info.framebuffer, color, _lcd.lcd_info.smem_len);
for (j = 0; j < _lcd.lcd_info.smem_len / 2; j++)
{
_lcd.lcd_info.framebuffer[j * 2] = data[0] ;
_lcd.lcd_info.framebuffer[j * 2 + 1] = data[1];
}
rt_spi_send(spi_dev_lcd, _lcd.lcd_info.framebuffer, _lcd.lcd_info.smem_len);
}
else
{
for (i = 0; i < LCD_HEIGHT; i++)
{
for (j = 0; j < LCD_WIDTH; j++)
{
rt_spi_send(spi_dev_lcd, data, 2);
}
}
}
}
/**
* @brief 點亮 LED 螢幕
* @param None
* @retval None
*/
static void lcd_display_on(void)
{
rt_pin_write(LCD_BCK_PIN, PIN_HIGH);
}
/**
* @brief 熄滅 LED 螢幕
* @param None
* @retval None
*/
static void lcd_display_off(void)
{
rt_pin_write(LCD_BCK_PIN, PIN_LOW);
}
/**
* @brief 液晶顯示器進入最小功耗模式,背光關閉
* @param None
* @retval None
*/
static void lcd_enter_sleep(void)
{
rt_pin_write(LCD_BCK_PIN, PIN_LOW);
rt_thread_mdelay(5);
lcd_write_cmd(0x10);
}
/**
* @brief 液晶顯示器關閉睡眠模式,背光燈開啟
* @param None
* @retval None
*/
static void lcd_exit_sleep(void)
{
rt_pin_write(LCD_BCK_PIN, PIN_HIGH);
rt_thread_mdelay(5);
lcd_write_cmd(0x11);
rt_thread_mdelay(120);
}
/**
* @brief 設定游標位置
* @param Xpos 橫座標
* @param Ypos 縱座標
* @retval None
*/
// static void lcd_cursor_set(rt_uint16_t Xpos, rt_uint16_t Ypos)
// {
// lcd_write_cmd(0x2A);
// lcd_write_data(Xpos>>8);
// lcd_write_data(Xpos&0XFF);
// lcd_write_cmd(0x2B);
// lcd_write_data(Ypos>>8);
// lcd_write_data(Ypos&0XFF);
// }
/**
* @brief LCD 驅動初始化
* @param device LCD 裝置結構體
* @retval None
*/
static rt_err_t drv_lcd_init(struct rt_device *device)
{
LOG_D("drv_lcd_init!\n");
if (lcd_spi_init() != RT_EOK)
{
return -RT_EINVAL;
}
lcd_gpio_init();
if (lcd_dev_init() != RT_EOK)
{
return -RT_EINVAL;
}
/* 清屏 */
lcd_clear_screen(WHITE);
/* 初始化完成後,點亮螢幕 */
rt_pin_write(LCD_BCK_PIN, PIN_HIGH);
return RT_EOK;
}
/**
* @brief LCD 驅動的操作函式
* @param device LCD 裝置結構體
* @param cmd 操作命令
* @param args 傳入的引數
* @retval None
*/
static rt_err_t drv_lcd_control(struct rt_device *device, int cmd, void *args)
{
// struct drv_lcd_device *lcd = LCD_DEVICE(device);
LOG_D("drv_lcd_control cmd is: %d\n", cmd);
switch (cmd)
{
case RTGRAPHIC_CTRL_RECT_UPDATE:
{
rt_sem_take(&_lcd.lcd_lock, RT_TICK_PER_SECOND / 20);
/* 重新整理緩衝區 */
if (_lcd.lcd_info.framebuffer)
{
/* 設定整個螢幕區域 */
lcd_draw_area_set(0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1);
/* 這裡直接透過 SPI 傳送資料,所以需要單獨將資料引腳拉高 */
rt_pin_write(LCD_DC_PIN, PIN_HIGH);
rt_spi_send(spi_dev_lcd, _lcd.lcd_info.framebuffer, _lcd.lcd_info.smem_len);
}
/* 釋放鎖訊號 */
rt_sem_release(&_lcd.lcd_lock);
}
break;
case RTGRAPHIC_CTRL_POWERON:
{
/* LCD 退出睡眠模式 */
lcd_display_on();
lcd_exit_sleep();
}
break;
case RTGRAPHIC_CTRL_POWEROFF:
{
/* LCD 進入睡眠模式 */
lcd_display_off();
lcd_enter_sleep();
}
break;
case RTGRAPHIC_CTRL_GET_INFO:
{
/* 獲取 LCD 引數 */
memcpy(args, &_lcd.lcd_info, sizeof(_lcd.lcd_info));
}
break;
default:
return -RT_EINVAL;
}
return RT_EOK;
}
/* 驅動函式實現的結構體 */
#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops lcd_ops =
{
drv_lcd_init,
RT_NULL,
RT_NULL,
RT_NULL,
RT_NULL,
drv_lcd_control
};
#endif
/**
* @brief LCD 裝置註冊
*
* @param None
* @retval int 註冊結果
*/
int drv_lcd_hw_init(void)
{
rt_err_t result = RT_EOK;
rt_uint32_t lcd_buff_size = lcd_buff_size = LCD_HEIGHT * LCD_WIDTH * 2;
/* 建立LCD裝置物件 */
struct rt_device *device = &_lcd.lcd_dev;
memset(&_lcd, 0x00, sizeof(_lcd));
LOG_D("drv_lcd_hw_init!\n");
/* 初始化lcd_lock訊號量 */
result = rt_sem_init(&_lcd.lcd_lock, "lcd_lock", 0, RT_IPC_FLAG_FIFO);
if (result != RT_EOK)
{
LOG_E("init semaphore failed!\n");
result = -RT_ENOMEM;
goto __exit;
}
/* 設定 LCD 裝置資訊 */
_lcd.lcd_info.height = LCD_HEIGHT;
_lcd.lcd_info.width = LCD_WIDTH;
_lcd.lcd_info.bits_per_pixel = LCD_BITS_PER_PIXEL;
_lcd.lcd_info.pixel_format = RTGRAPHIC_PIXEL_FORMAT_RGB565; // 影像的格式(RGB:565)
/* LCD 顯示緩衝區,大小為顯示一幀影像所需空間 */
_lcd.lcd_info.smem_len = lcd_buff_size;
_lcd.lcd_info.framebuffer = rt_malloc(lcd_buff_size);
if (_lcd.lcd_info.framebuffer == RT_NULL)
{
LOG_E("init frame buffer failed!\n");
result = -RT_ENOMEM;
goto __exit;
}
/* 將緩衝區初始化為 0xFF */
memset(_lcd.lcd_info.framebuffer, 0xFF, lcd_buff_size);
#ifdef RT_USING_DEVICE_OPS
device->ops = &lcd_ops;
#else
device->init = drv_lcd_init;
device->control = drv_lcd_control;
#endif
/* 註冊 LCD 裝置 */
rt_device_register(device, "lcd", RT_DEVICE_FLAG_RDWR);
__exit:
if (result != RT_EOK)
{
rt_sem_detach(&_lcd.lcd_lock);
if (_lcd.lcd_info.framebuffer)
{
rt_free(_lcd.lcd_info.framebuffer);
}
}
return result;
}
INIT_DEVICE_EXPORT(drv_lcd_hw_init);
#endif /* BSP_USING_LCD */