找回密码
 立即注册
首页 业界区 安全 龙芯2k0300 - 走马观碑组ST7735驱动移植

龙芯2k0300 - 走马观碑组ST7735驱动移植

韶侪 4 小时前
在《第21届智能汽车竞赛走马观碑软硬件设计》中我们介绍到我们的开发板使用了1.8寸TFT显示模块,显示模块使用的LCD驱动芯片为ST7735,屏幕分辨率为128*160,尺寸为1.8寸,屏幕显示接口采用SPI通信方式。
1.png
一、ST7735设备驱动

SPI驱动源码移植可参考:

  • 《通信协议-SPI》;
  • 《linux驱动移植-SPI总线设备驱动》;
  • 《linux驱动移植-SPI控制器驱动》;
  • 《linux驱动移植-SPI驱动移植(OLED SSD1306)》。
龙邱科技提供的驱动源码位于:TFT18_Driver。实际上我们下载的内核linux 6.12已经内置了ST7735的驱动,这里我们就直接使用内核驱动即可,驱动源码位于drivers/staging/fbtft/fb_st7735r.c。
1.1 ST7735驱动

1.1.1 fb_st7735r.c

drivers/staging/fbtft/fb_st7735r.c源码如下:
点击查看详情
  1. // SPDX-License-Identifier: GPL-2.0+
  2. /*
  3. * FB driver for the ST7735R LCD Controller
  4. *
  5. * Copyright (C) 2013 Noralf Tronnes
  6. */
  7. #include <linux/module.h>
  8. #include <linux/kernel.h>
  9. #include <linux/init.h>
  10. #include <video/mipi_display.h>
  11. #include "fbtft.h"
  12. #define DRVNAME "fb_st7735r"
  13. #define DEFAULT_GAMMA   "0F 1A 0F 18 2F 28 20 22 1F 1B 23 37 00 07 02 10\n" \
  14.                         "0F 1B 0F 17 33 2C 29 2E 30 30 39 3F 00 07 03 10"
  15. static const s16 default_init_sequence[] = {
  16.         -1, MIPI_DCS_SOFT_RESET,
  17.         -2, 150,                               /* delay */
  18.         -1, MIPI_DCS_EXIT_SLEEP_MODE,
  19.         -2, 500,                               /* delay */
  20.         /* FRMCTR1 - frame rate control: normal mode
  21.          * frame rate = fosc / (1 x 2 + 40) * (LINE + 2C + 2D)
  22.          */
  23.         -1, 0xB1, 0x01, 0x2C, 0x2D,
  24.         /* FRMCTR2 - frame rate control: idle mode
  25.          * frame rate = fosc / (1 x 2 + 40) * (LINE + 2C + 2D)
  26.          */
  27.         -1, 0xB2, 0x01, 0x2C, 0x2D,
  28.         /* FRMCTR3 - frame rate control - partial mode
  29.          * dot inversion mode, line inversion mode
  30.          */
  31.         -1, 0xB3, 0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D,
  32.         /* INVCTR - display inversion control
  33.          * no inversion
  34.          */
  35.         -1, 0xB4, 0x07,
  36.         /* PWCTR1 - Power Control
  37.          * -4.6V, AUTO mode
  38.          */
  39.         -1, 0xC0, 0xA2, 0x02, 0x84,
  40.         /* PWCTR2 - Power Control
  41.          * VGH25 = 2.4C VGSEL = -10 VGH = 3 * AVDD
  42.          */
  43.         -1, 0xC1, 0xC5,
  44.         /* PWCTR3 - Power Control
  45.          * Opamp current small, Boost frequency
  46.          */
  47.         -1, 0xC2, 0x0A, 0x00,
  48.         /* PWCTR4 - Power Control
  49.          * BCLK/2, Opamp current small & Medium low
  50.          */
  51.         -1, 0xC3, 0x8A, 0x2A,
  52.         /* PWCTR5 - Power Control */
  53.         -1, 0xC4, 0x8A, 0xEE,
  54.         /* VMCTR1 - Power Control */
  55.         -1, 0xC5, 0x0E,
  56.         -1, MIPI_DCS_EXIT_INVERT_MODE,
  57.         -1, MIPI_DCS_SET_PIXEL_FORMAT, MIPI_DCS_PIXEL_FMT_16BIT,
  58.         -1, MIPI_DCS_SET_DISPLAY_ON,
  59.         -2, 100,                               /* delay */
  60.         -1, MIPI_DCS_ENTER_NORMAL_MODE,
  61.         -2, 10,                               /* delay */
  62.         /* end marker */
  63.         -3
  64. };
  65. static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
  66. {
  67.         write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS,
  68.                   xs >> 8, xs & 0xFF, xe >> 8, xe & 0xFF);
  69.         write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS,
  70.                   ys >> 8, ys & 0xFF, ye >> 8, ye & 0xFF);
  71.         write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
  72. }
  73. #define MY BIT(7)
  74. #define MX BIT(6)
  75. #define MV BIT(5)
  76. static int set_var(struct fbtft_par *par)
  77. {
  78.         /* MADCTL - Memory data access control
  79.          * RGB/BGR:
  80.          * 1. Mode selection pin SRGB
  81.          *    RGB H/W pin for color filter setting: 0=RGB, 1=BGR
  82.          * 2. MADCTL RGB bit
  83.          *    RGB-BGR ORDER color filter panel: 0=RGB, 1=BGR
  84.          */
  85.         switch (par->info->var.rotate) {
  86.         case 0:
  87.                 write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
  88.                           MX | MY | (par->bgr << 3));
  89.                 break;
  90.         case 270:
  91.                 write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
  92.                           MY | MV | (par->bgr << 3));
  93.                 break;
  94.         case 180:
  95.                 write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
  96.                           par->bgr << 3);
  97.                 break;
  98.         case 90:
  99.                 write_reg(par, MIPI_DCS_SET_ADDRESS_MODE,
  100.                           MX | MV | (par->bgr << 3));
  101.                 break;
  102.         }
  103.         return 0;
  104. }
  105. /*
  106. * Gamma string format:
  107. * VRF0P VOS0P PK0P PK1P PK2P PK3P PK4P PK5P PK6P PK7P PK8P PK9P SELV0P SELV1P SELV62P SELV63P
  108. * VRF0N VOS0N PK0N PK1N PK2N PK3N PK4N PK5N PK6N PK7N PK8N PK9N SELV0N SELV1N SELV62N SELV63N
  109. */
  110. #define CURVE(num, idx)  curves[(num) * par->gamma.num_values + (idx)]
  111. static int set_gamma(struct fbtft_par *par, u32 *curves)
  112. {
  113.         int i, j;
  114.         /* apply mask */
  115.         for (i = 0; i < par->gamma.num_curves; i++)
  116.                 for (j = 0; j < par->gamma.num_values; j++)
  117.                         CURVE(i, j) &= 0x3f;
  118.         for (i = 0; i < par->gamma.num_curves; i++)
  119.                 write_reg(par, 0xE0 + i,
  120.                           CURVE(i, 0),  CURVE(i, 1),
  121.                           CURVE(i, 2),  CURVE(i, 3),
  122.                           CURVE(i, 4),  CURVE(i, 5),
  123.                           CURVE(i, 6),  CURVE(i, 7),
  124.                           CURVE(i, 8),  CURVE(i, 9),
  125.                           CURVE(i, 10), CURVE(i, 11),
  126.                           CURVE(i, 12), CURVE(i, 13),
  127.                           CURVE(i, 14), CURVE(i, 15));
  128.         return 0;
  129. }
  130. #undef CURVE
  131. static struct fbtft_display display = {
  132.         .regwidth = 8,
  133.         .width = 128,
  134.         .height = 160,
  135.         .init_sequence = default_init_sequence,
  136.         .gamma_num = 2,
  137.         .gamma_len = 16,
  138.         .gamma = DEFAULT_GAMMA,
  139.         .fbtftops = {
  140.                 .set_addr_win = set_addr_win,
  141.                 .set_var = set_var,
  142.                 .set_gamma = set_gamma,
  143.         },
  144. };
  145. FBTFT_REGISTER_DRIVER(DRVNAME, "sitronix,st7735r", &display);
  146. MODULE_ALIAS("spi:" DRVNAME);
  147. MODULE_ALIAS("platform:" DRVNAME);
  148. MODULE_ALIAS("spi:st7735r");
  149. MODULE_ALIAS("platform:st7735r");
  150. MODULE_DESCRIPTION("FB driver for the ST7735R LCD Controller");
  151. MODULE_AUTHOR("Noralf Tronnes");
  152. MODULE_LICENSE("GPL");
复制代码
1.1.2 FBTFT框架介绍

这里采用了FBTFT 框架,一个基于fbdev的辅助框架,位于drivers/staging/fbtft/,专门为小尺寸SPI/并口LCD控制器(如 ST7735、ILI9341、SSD1306等)设计;
维度Framebuffer (fbdev)FBTFT层次底层框架基于 fbdev 的中间件复杂度需要实现完整的 fb_ops只需配置结构体,框架代劳适用范围所有显示硬件小尺寸 SPI/并口 LCD 控制器输出产物驱动直接注册 /dev/fbX驱动通过 FBTFT 注册 /dev/fbX代码量较大极少(通常几百行)例子drivers/video/fbdev/ 下的各种驱动drivers/staging/fbtft/fb_st7735r.c以 fb_st7735r.c 驱动为例,它没有直接调用register_framebuffer,而是通过宏FBTFT_REGISTER_DRIVER将设备描述结构体传递给 FBTFT框架。FBTFT框架内部完成:

  • 分配 fb_info 结构体;
  • 填充 fb_ops(使用框架内的通用函数);
  • 调用 register_framebuffer 向系统注册fbdev设备。
因此,用户空间看到的/dev/fb0仍然是一个标准的framebuffer设备,只不过背后的实现由FBTFT框架统一管理。
如果对framebuffer设备驱动框架图感兴趣可以参考:《linux驱动移植-LCD驱动基础》。
1.2  内核配置

进入内核配置界面:
  1. zhengyang@ubuntu:/opt/2k0300/build-2k0300/workspace/linux-6.12$ cd ~
  2. zhengyang@ubuntu:~$ cd /opt/2k0300/build-2k0300/workspace/linux-6.12
  3. zhengyang@ubuntu:/opt/2k0300/build-2k0300/workspace/linux-6.12$ source ../set_env.sh && make menuconfig
复制代码
依次进入以下菜单:
  1. Device Drivers  →
  2.      [*]  Staging drivers  →
  3.         [*]   Support for small TFT LCD display modules  →
  4.                 <*>   FB driver for the ST7735R LCD Controller
  5.      [*]  SPI support  →   
  6.             <*>   Loongson SPI Controller Platform Driver Support
复制代码
默认会生成配置:
  1. CONFIG_FB_TFT_ST7735R=y
  2. CONFIG_SPI_LOONGSON_PLATFORM=y
复制代码
我们直接修改arch/loongarch/configs/loongson_2k300_defconfig文件,加入这两个配置。
1.3 新增设备节点

这里我们将显示模块接到spi1接口,因此需要适当调整设备树。
1.3.1 spi1

spi1节点定义在arch/loongarch/boot/dts/loongson-2k0300.dtsi:
  1. spi1: spi@0x16018000 {
  2.         compatible = "loongson,ls-spi";
  3.         reg = <0 0x16018000 0 0x10>;
  4.         #address-cells = <1>;
  5.         #size-cells = <0>;
  6.         interrupt-parent = <&liointc1>;
  7.         interrupts = <13 IRQ_TYPE_LEVEL_HIGH>; // 45 - 32 = 13
  8.         clock-frequency = <200000000>;
  9.         pinctrl-0 = <&spi1_pins>;
  10.         pinctrl-names = "default";
  11.         status = "disabled";
  12. };
复制代码
其中:

  • spi1::标签,用于在设备树中其他地方通过&spi1引用这个节点,方便添加属性或修改状态;
  • spi@0x16018000:节点名,格式为 设备名@寄存器基地址。这里spi是功能名,0x16108000是该SPI控制器的物理寄存器基地址。
  • compatible:驱动匹配字符串。内核通过该属性寻找能驱动此设备的驱动程序;
  • reg:寄存器地址范围。格式为 ,由于龙芯采用64位寻址,这里用两个32位数表示64位地址;
  • #address-cells和#size-cells:定义该节点下子节点(即挂载的SPI从设备)的地址和长度格式。

    • #address-cells = :子节点的 reg 属性中,地址部分占用1个32位单元;
    • #size-cells = :子节点的 reg 属性中没有长度字段;

  • interrupt-parent:指定该设备的中断路由到哪个中断控制器。&liointc0 是龙芯2K0300内部的中断控制器节点(即龙芯I/O中断控制器)的标签;
  • interrupts:描述中断线的具体信息;

    • :硬件中断编号(对应SPI1控制器在中断控制器中的编号);
    • IRQ_TYPE_LEVEL_HIGH:中断触发类型,这里定义为高电平触发。该宏在  中定义。

  • pinctrl-0:指定设备使用的第一组引脚配置,&spi1_pins 是另一个设备树节点的标签,该节点描述了SPI的MOSI和MISO引脚应复用为SPI功能,并可能包含上拉等电气属性;
  • pinctrl-names:为引脚的配置状态命名,与 pinctrl-0 对应。"default" 是默认状态,驱动在probe时会自动应用 pinctrl-0 的配置,引脚配置设置为&spi1_pins;
  • clock-frequency:SPI控制器的输入时钟频率(单位Hz);
  • status:设备状态,此时处于禁用状态。
更多有关设备树相关的内容可以参考:《linux设备树-基础介绍》。
1.3.2 st7735r

修改arch/loongarch/boot/dts/ls2k300_99pi.dtsi:
  1. &spi1 {
  2.         status = "okay";
  3.         cs-gpios = <&gpio 63 GPIO_ACTIVE_LOW>;     // CS引脚,根据实际修改
  4.         // 屏幕驱动
  5.         st7735r@0{
  6.                 status = "okay";
  7.                 compatible = "sitronix,st7735r";
  8.                 reg = <0>;
  9.                 spi-max-frequency = <100000000>;
  10.                 fps = <60>;
  11.                 dc-gpios = <&gpio 48 GPIO_ACTIVE_HIGH>;
  12.                 reset-gpios = <&gpio 49 GPIO_ACTIVE_LOW>;
  13.                 rotate = <90>;
  14.                 buswidth = <8>;
  15.         };
  16. };
复制代码
通过 &spi1 引用这个节点,将 status 改为 "okay",同时添加了SPI从设备节点。
其中st7735r@0设备节点下的compatible名称要和fb_st7735r.c驱动中的名称匹配。
1.3.3 spi1_pins

spi1_pins定义在arch/loongarch/boot/dts/loongson-2k0300.dtsi:
  1. pinmux: pinmux@16000490 {
  2.         ......
  3.         spi1_pins: pinmux_G60_G63_as_spi1 {
  4.             pinctrl-single,bits = <0xc 0xff000000 0xff000000>;
  5.     };
  6.         ......
  7. }
复制代码
这个设备树节点是使用pinctrl-single驱动来配置引脚复用功能的典型写法,这行代码会在寄存器0x1600049c的高8位写入 0xff,而低24位保持不变,用于将芯片的GPIO0~GPIO62这四个引脚设置为SPI1功能。
2.png
注:pinctrl-single 是一个通用的引脚控制驱动,适用于那些引脚复用寄存器是“单寄存器位域”的芯片。当SoC没有提供复杂的pinctrl框架时,可以直接用这种方式“裸写”寄存器。
二、应用程序

接下来我们在example目录下创建子目录st7735_app;
  1. zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/example$ mkdir st7735_app
复制代码
目录结构如下:
  1. zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/example/st7735_app$ tree .
  2. .
  3. ├── main.c
  4. └── Makefile
复制代码
2.1 main.c

ST7735驱动注册为帧缓冲设备/dev/fb0。应用程序可以通过mmap将屏幕显存映射到用户空间,然后直接写入像素数据。
下面是一个完整的C程序,实现屏幕初始化、清屏、绘制像素、显示文字等基本功能,编译后可在帧缓冲设备(/dev/fb0)上运行;
点击查看详情
  1. /*
  2. * 测试 ST7735 屏幕(通过 /dev/fb0)的简单图形和文字显示
  3. */
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <unistd.h>
  7. #include <fcntl.h>
  8. #include <sys/ioctl.h>
  9. #include <sys/mman.h>
  10. #include <linux/fb.h>
  11. #include <string.h>
  12. static int fb_fd;
  13. static struct fb_var_screeninfo vinfo;
  14. static struct fb_fix_screeninfo finfo;
  15. static uint16_t *fb_mem = NULL;
  16. static size_t fb_size;
  17. static int fb_width, fb_height;
  18. // 颜色宏:RGB565 格式
  19. #define RGB565(r,g,b) ((((r)>>3)<<11) | (((g)>>2)<<5) | ((b)>>3))
  20. #define COLOR_RED     RGB565(255,0,0)
  21. #define COLOR_GREEN   RGB565(0,255,0)
  22. #define COLOR_BLUE    RGB565(0,0,255)
  23. #define COLOR_WHITE   RGB565(255,255,255)
  24. #define COLOR_BLACK   0x0000
  25. // 初始化帧缓冲设备
  26. int fb_init(void) {
  27.     fb_fd = open("/dev/fb0", O_RDWR);
  28.     if (fb_fd < 0) {
  29.         perror("open /dev/fb0");
  30.         return -1;
  31.     }
  32.     if (ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo) < 0) {
  33.         perror("ioctl FBIOGET_VSCREENINFO");
  34.         close(fb_fd);
  35.         return -1;
  36.     }
  37.     if (ioctl(fb_fd, FBIOGET_FSCREENINFO, &finfo) < 0) {
  38.         perror("ioctl FBIOGET_FSCREENINFO");
  39.         close(fb_fd);
  40.         return -1;
  41.     }
  42.     fb_width = vinfo.xres;
  43.     fb_height = vinfo.yres;
  44.     fb_size = fb_width * fb_height * vinfo.bits_per_pixel / 8;
  45.     printf("FB: %dx%d, %d bpp, line_len=%d\n", fb_width, fb_height, vinfo.bits_per_pixel, finfo.line_length);
  46.     fb_mem = mmap(0, fb_size, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0);
  47.     if (fb_mem == MAP_FAILED) {
  48.         perror("mmap");
  49.         close(fb_fd);
  50.         return -1;
  51.     }
  52.     return 0;
  53. }
  54. // 关闭帧缓冲设备
  55. void fb_deinit(void) {
  56.     if (fb_mem) munmap(fb_mem, fb_size);
  57.     if (fb_fd >= 0) close(fb_fd);
  58. }
  59. // 绘制像素(x, y)坐标,颜色 color(RGB565)
  60. void fb_draw_pixel(int x, int y, uint16_t color) {
  61.     if (x >= 0 && x < fb_width && y >= 0 && y < fb_height)
  62.         fb_mem[y * fb_width + x] = color;
  63. }
  64. // 清屏为指定颜色
  65. void fb_clear(uint16_t color) {
  66.     for (int i = 0; i < fb_width * fb_height; i++)
  67.         fb_mem[i] = color;
  68. }
  69. // 绘制矩形(填充)
  70. void fb_fill_rect(int x, int y, int w, int h, uint16_t color) {
  71.     int x1 = (x < 0) ? 0 : x;
  72.     int y1 = (y < 0) ? 0 : y;
  73.     int x2 = (x + w > fb_width) ? fb_width : x + w;
  74.     int y2 = (y + h > fb_height) ? fb_height : y + h;
  75.     for (int i = y1; i < y2; i++)
  76.         for (int j = x1; j < x2; j++)
  77.             fb_mem[i * fb_width + j] = color;
  78. }
  79. // 绘制空心矩形框
  80. void fb_draw_rect(int x, int y, int w, int h, uint16_t color) {
  81.     fb_fill_rect(x, y, w, 1, color);      // 上边
  82.     fb_fill_rect(x, y + h - 1, w, 1, color); // 下边
  83.     fb_fill_rect(x, y, 1, h, color);      // 左边
  84.     fb_fill_rect(x + w - 1, y, 1, h, color); // 右边
  85. }
  86. // 简易 8x8 字符点阵(仅 ASCII 可打印字符部分示例,这里只提供数字和部分字母)
  87. static uint8_t font_8x8[95][8] = {0}; // 实际上需要完整字库,这里简化,只实现常用字母数字
  88. // 预置一些常用字符(仅示例,实际需完整)
  89. static uint8_t char_A[8] = {0x00, 0x18, 0x24, 0x42, 0x7E, 0x42, 0x42, 0x00};
  90. static uint8_t char_B[8] = {0x7C, 0x42, 0x7C, 0x42, 0x42, 0x7C, 0x00, 0x00};
  91. static uint8_t char_C[8] = {0x3C, 0x42, 0x40, 0x40, 0x40, 0x3C, 0x00, 0x00};
  92. static uint8_t char_D[8] = {0x78, 0x44, 0x42, 0x42, 0x44, 0x78, 0x00, 0x00};
  93. static uint8_t char_E[8] = {0x7E, 0x40, 0x7C, 0x40, 0x40, 0x7E, 0x00, 0x00};
  94. static uint8_t char_F[8] = {0x7E, 0x40, 0x7C, 0x40, 0x40, 0x40, 0x00, 0x00};
  95. static uint8_t char_G[8] = {0x3C, 0x42, 0x40, 0x4E, 0x42, 0x3C, 0x00, 0x00};
  96. static uint8_t char_H[8] = {0x42, 0x42, 0x7E, 0x42, 0x42, 0x42, 0x00, 0x00};
  97. static uint8_t char_I[8] = {0x3E, 0x08, 0x08, 0x08, 0x08, 0x3E, 0x00, 0x00};
  98. static uint8_t char_J[8] = {0x1F, 0x04, 0x04, 0x04, 0x44, 0x38, 0x00, 0x00};
  99. static uint8_t char_K[8] = {0x42, 0x44, 0x48, 0x70, 0x48, 0x44, 0x42, 0x00};
  100. static uint8_t char_L[8] = {0x40, 0x40, 0x40, 0x40, 0x40, 0x7E, 0x00, 0x00};
  101. static uint8_t char_M[8] = {0x42, 0x66, 0x5A, 0x42, 0x42, 0x42, 0x00, 0x00};
  102. static uint8_t char_N[8] = {0x42, 0x62, 0x52, 0x4A, 0x46, 0x42, 0x00, 0x00};
  103. static uint8_t char_O[8] = {0x3C, 0x42, 0x42, 0x42, 0x42, 0x3C, 0x00, 0x00};
  104. static uint8_t char_P[8] = {0x7C, 0x42, 0x42, 0x7C, 0x40, 0x40, 0x00, 0x00};
  105. static uint8_t char_Q[8] = {0x3C, 0x42, 0x42, 0x52, 0x4C, 0x32, 0x00, 0x00};
  106. static uint8_t char_R[8] = {0x7C, 0x42, 0x42, 0x7C, 0x44, 0x42, 0x00, 0x00};
  107. static uint8_t char_S[8] = {0x3C, 0x42, 0x20, 0x1C, 0x02, 0x7C, 0x00, 0x00};
  108. static uint8_t char_T[8] = {0x7E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00};
  109. static uint8_t char_U[8] = {0x42, 0x42, 0x42, 0x42, 0x42, 0x3C, 0x00, 0x00};
  110. static uint8_t char_V[8] = {0x42, 0x42, 0x42, 0x24, 0x24, 0x18, 0x00, 0x00};
  111. static uint8_t char_W[8] = {0x42, 0x42, 0x42, 0x5A, 0x66, 0x42, 0x00, 0x00};
  112. static uint8_t char_X[8] = {0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 0x00};
  113. static uint8_t char_Y[8] = {0x42, 0x24, 0x18, 0x08, 0x08, 0x08, 0x00, 0x00};
  114. static uint8_t char_Z[8] = {0x7E, 0x02, 0x04, 0x08, 0x10, 0x7E, 0x00, 0x00};
  115. static uint8_t char_0[8] = {0x3C, 0x42, 0x46, 0x4A, 0x52, 0x62, 0x3C, 0x00};
  116. static uint8_t char_1[8] = {0x08, 0x18, 0x08, 0x08, 0x08, 0x08, 0x1C, 0x00};
  117. static uint8_t char_2[8] = {0x3C, 0x42, 0x02, 0x0C, 0x30, 0x40, 0x7E, 0x00};
  118. static uint8_t char_3[8] = {0x3C, 0x42, 0x02, 0x1C, 0x02, 0x42, 0x3C, 0x00};
  119. static uint8_t char_4[8] = {0x08, 0x18, 0x28, 0x48, 0x7E, 0x08, 0x08, 0x00};
  120. static uint8_t char_5[8] = {0x7E, 0x40, 0x7C, 0x02, 0x02, 0x42, 0x3C, 0x00};
  121. static uint8_t char_6[8] = {0x3C, 0x40, 0x7C, 0x42, 0x42, 0x42, 0x3C, 0x00};
  122. static uint8_t char_7[8] = {0x7E, 0x02, 0x04, 0x08, 0x10, 0x10, 0x10, 0x00};
  123. static uint8_t char_8[8] = {0x3C, 0x42, 0x42, 0x3C, 0x42, 0x42, 0x3C, 0x00};
  124. static uint8_t char_9[8] = {0x3C, 0x42, 0x42, 0x3E, 0x02, 0x42, 0x3C, 0x00};
  125. void fb_draw_char(int x, int y, char ch, uint16_t fg, uint16_t bg) {
  126.     uint8_t *glyph = NULL;
  127.     if (ch >= 'A' && ch <= 'Z') {
  128.         switch(ch) {
  129.             case 'A': glyph = char_A; break;
  130.             case 'B': glyph = char_B; break;
  131.             case 'C': glyph = char_C; break;
  132.             case 'D': glyph = char_D; break;
  133.             case 'E': glyph = char_E; break;
  134.             case 'F': glyph = char_F; break;
  135.             case 'G': glyph = char_G; break;
  136.             case 'H': glyph = char_H; break;
  137.             case 'I': glyph = char_I; break;
  138.             case 'J': glyph = char_J; break;
  139.             case 'K': glyph = char_K; break;
  140.             case 'L': glyph = char_L; break;
  141.             case 'M': glyph = char_M; break;
  142.             case 'N': glyph = char_N; break;
  143.             case 'O': glyph = char_O; break;
  144.             case 'P': glyph = char_P; break;
  145.             case 'Q': glyph = char_Q; break;
  146.             case 'R': glyph = char_R; break;
  147.             case 'S': glyph = char_S; break;
  148.             case 'T': glyph = char_T; break;
  149.             case 'U': glyph = char_U; break;
  150.             case 'V': glyph = char_V; break;
  151.             case 'W': glyph = char_W; break;
  152.             case 'X': glyph = char_X; break;
  153.             case 'Y': glyph = char_Y; break;
  154.             case 'Z': glyph = char_Z; break;
  155.         }
  156.     } else if (ch >= '0' && ch <= '9') {
  157.         switch(ch) {
  158.             case '0': glyph = char_0; break;
  159.             case '1': glyph = char_1; break;
  160.             case '2': glyph = char_2; break;
  161.             case '3': glyph = char_3; break;
  162.             case '4': glyph = char_4; break;
  163.             case '5': glyph = char_5; break;
  164.             case '6': glyph = char_6; break;
  165.             case '7': glyph = char_7; break;
  166.             case '8': glyph = char_8; break;
  167.             case '9': glyph = char_9; break;
  168.         }
  169.     } else {
  170.         // 其他字符(空格等)不绘制
  171.         return;
  172.     }
  173.     if (!glyph) return;
  174.     for (int row = 0; row < 8; row++) {
  175.         uint8_t line = glyph[row];
  176.         for (int col = 0; col < 8; col++) {
  177.             if (line & (0x80 >> col)) {
  178.                 fb_draw_pixel(x + col, y + row, fg);
  179.             } else {
  180.                 fb_draw_pixel(x + col, y + row, bg);
  181.             }
  182.         }
  183.     }
  184. }
  185. void fb_draw_string(int x, int y, const char *str, uint16_t fg, uint16_t bg) {
  186.     int cx = x;
  187.     int cy = y;
  188.     for (int i = 0; str[i]; i++) {
  189.         if (str[i] == '\n') {
  190.             cx = x;
  191.             cy += 9; // 8像素高度+1行间距
  192.             continue;
  193.         }
  194.         fb_draw_char(cx, cy, str[i], fg, bg);
  195.         cx += 9; // 8像素宽度+1间距
  196.         if (cx + 8 > fb_width) {
  197.             cx = x;
  198.             cy += 9;
  199.         }
  200.     }
  201. }
  202. // 简单测试:显示一些图形和文字
  203. void test_display(void) {
  204.     fb_clear(COLOR_BLACK);
  205.     // 画红色填充矩形
  206.     fb_fill_rect(10, 10, 100, 50, COLOR_RED);
  207.     // 画蓝色边框
  208.     fb_draw_rect(120, 10, 100, 50, COLOR_BLUE);
  209.     // 显示文字
  210.     fb_draw_string(10, 80, "Hello", COLOR_WHITE, COLOR_BLACK);
  211.     fb_draw_string(10, 95, "ST7735", COLOR_GREEN, COLOR_BLACK);
  212.     fb_draw_string(10, 110, "LCD", COLOR_BLUE, COLOR_BLACK);
  213.     // 显示数字和字母混合
  214.     fb_draw_string(10, 130, "A1B2C3", COLOR_RED, COLOR_BLACK);
  215. }
  216. int main(void) {
  217.     if (fb_init() != 0) {
  218.         fprintf(stderr, "Failed to init framebuffer\n");
  219.         return 1;
  220.     }
  221.     test_display();
  222.     // 保持显示10秒后退出(或按任意键)
  223.     printf("Display test. Press Enter to exit...\n");
  224.     getchar();
  225.     fb_deinit();
  226.     return 0;
  227. }
复制代码
2.2 Makefile
  1. all:
  2.         loongarch64-linux-gnu-gcc -o main main.c
  3. clean:
  4.         rm -rf *.o main
复制代码
2.3 编译应用程序
  1. zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/example/st7735_app$ make
  2. loongarch64-linux-gnu-gcc -o main main.c
  3. zhengyang@ubuntu:/opt/2k0300/loongson_2k300_lib/example/st7735_app$ ll
  4. -rwxrwxr-x 1 zhengyang zhengyang 20680  3月 24 14:26 main*
  5. -rw-rw-r-- 1 zhengyang zhengyang  1070  3月 24 14:23 main.c
  6. -rw-rw-r-- 1 zhengyang zhengyang    71  3月 24 14:24 Makefile
复制代码
三、测试

3.1 硬件接线

显示模块与龙芯2K0300开发板正确连接,接线如下:
屏幕引脚功能建议连接的龙芯GPIO说明VCC电源3.3V屏幕电源GND地GNDD0SCLKSPI1_SCLK (GPIO60)SPI总线时钟D1MOSISPI1_MOSI(GPIO62)SPI主机输出从机输入DC命令/数据选择GPIO48用于区分命令和数据RST复位GPIO49屏幕复位引脚CS片选SPI1_CSn(GPIO63)SPI片选信号3.2 烧录设备树

有关设备树的编译和烧录分别参考:

  • 《编译设备树》;
  • 《更新设备树》。
3.3 安装驱动

由于我们在内核配置环节将驱动配置到内核中了,因此需要重新编译内核并烧录内核。
3.3.1 编译内核

ubuntu宿主接重新编译内核:
  1. zhengyang@ubuntu:/opt/2k0300/build-2k0300/workspace/linux-6.12$ source ../set_env.sh && make uImage -j$(nproc)
复制代码
3.3.2 烧录内核

久久派烧录内核:
  1. root@buildroot:~$ scp zhengyang@172.23.34.186:/opt/2k0300/build-2k0300/workspace/linux-6.12/arch/loongarch/boot/uImage /boot/
  2. root@buildroot:~$ reboot
复制代码
查看设备节点文件:
  1. root@buildroot:~$ ls /dev/fb0
复制代码
3.4 应用程序测试

久久派开发板执行如下命令:
  1. root@buildroot:~$ scp zhengyang@172.23.34.186:/opt/2k0300/loongson_2k300_lib/example/st7735_app/main ./
  2. root@buildroot:~$ ./main
复制代码
参考文章
[1] Linux内核分析——基于LoongArch架构(Linux-6.0).pdf
[2] 第一卷-pmon软件使用手册.pdf
[3] 第二卷-pinctrl与dts开发手册.pdf
[4] 第三卷-2k300内核开发手册.pdf
[5] 第四卷-文件系统开发手册.pdf
[6] 第五卷-qt相关解决方案.pdf
[7] LS2K0300久久派_V1.1板卡使用手册v1.2_20240705.pdf
[8]  龙芯2K300_301软件开源库
[9] 21届智能车走马观碑开源仓库

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册