3

STM32 + RTThread + UGUI - 浇筑菜鸟

 1 year ago
source link: https://www.cnblogs.com/jzcn/p/17362173.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
  • 开发板: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 驱动,所以这里我们只能根据标准的驱动进行编写了

  1. 驱动函数结构体

    /* 驱动函数实现的结构体 */#ifdef RT_USING_DEVICE_OPSconst static struct rt_device_ops lcd_ops ={ drv_lcd_init, RT_NULL, RT_NULL, RT_NULL, RT_NULL, drv_lcd_control};#endif
  2. 注册 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; }
  3. 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;}
  4. LCD 驱动功能实现
    剩下的就比较简单了,只需要参考 LCD 提供的案例程序进行更改就好了,主要有实现如下

    • drv_lcd_init: 完成 LCD 的复位、初始化、首次清屏工作
    • drv_lcd_control: 完成 LCD 显示区域的刷新、屏幕参数的返回、亮屏和息屏等工作
      注意:具体实现参考后面的程序源码,相对比较简单,这里就不过多介绍了

四、UGUI 介绍

  1. 介绍
    µGUI 是一个用于嵌入式系统的免费开源图形库。 它独立于平台,可以轻松移植到几乎任何微控制器系统。 只要显示器能够显示图形,μGUI 就不受某种显示技术的限制。 因此,支持LCD、TFT、E-Paper、LED或OLED等显示技术。 整个模块由两个文件组成:ugui.c 和 ugui.h。
    注意:这里的介绍我直接引用了作者的描述

  2. 获取 UGUI
    github:https://github.com/xidongxu/ugui

  3. 文件目录

    2406897-20230428151813843-1442366347.png
    • 移植: 我们主要实现 ugui_port.c,这里下载时已经提供了案例,所以我只需要在其中进行简单的修改
    • 使用: 使用相对比较简单,直接参考 “µGUI v0.3.pdf” 文档即可,直接没有难度

五、UGUI 移植

  1. 初始化
    直接在 ugui_port.c 文件中使用 INIT_COMPONENT_EXPORT(ugui_port_init) 进行自动初始化,如下图所示:

    2406897-20230428153020926-1431820326.png
  2. lcd_open 函数
    这里不要做任何更改,从函数中可以看出 LCD 相关的参数获取,如下图所示:

    2406897-20230428153405197-1734470591.png
  3. lcd_draw_pixel 函数
    主要功能是在缓冲区中写入一个像素点的颜色,如下图所示:

    2406897-20230428153709583-782162988.png
  4. ugui_port_thread_entry 函数
    这是线程的入口函数,主要目的是定期将缓冲区的数据写入到 LCD 中,下图所示:
    2406897-20230428153946559-1618333601.png

注意:从以上步奏可以看出,移植 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_OPSconst 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 */

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK