Frame Buffer设备驱动 (ili9488 3.5寸tft屏)

Frame Buffer设备驱动

  • Frame Buffer设备
  • ili9488介绍
  • 驱动编写
  • 代码编写
    • ili9488.c
    • 设备树修改
    • 测试
    • ili9488代码分析
  • LCD资料下载

Frame Buffer设备

  在早期的输出显示设备中,大部分为CRT显示器,随着技术的不断发展,现在大部分使用的是液晶显示器。这些显示设备在linux中统称为Frame Buffer设备,即帧缓冲设备,简称FB。
  需要说明的是,并不是只有显示屏这种设备才是帧缓存设备,实际上帧缓冲设备只是一种显示原理,将显示缓冲区中的数据重定向到输出设备中,也许有些输出设备仅仅只是一个虚拟的设备,那为何叫帧缓冲设备?这主要是因为帧缓冲设备的显示原理是在内存中开辟一块特定的暂存区,这块暂存区记录着所有像素数据,在一定的时间内将这个数据一一对应的写入到输出设备中,这样输出设备上就是我们需要显示的信息了。

ili9488介绍

  对于我购买的3.5寸TFT显示屏,其驱动芯片是ILI9488,采用的4线制spi,
在这里插入图片描述
其数据传输方式如下图所示
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

驱动编写

驱动主要是根据厂商提供的stm32例程来改的,在该例程里面有如下函数
在这里插入图片描述
从这里可以看出他是RGB565转的RGB666,在编写代码的时候也采用同样的思路

代码编写

ili9488.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <asm/io.h> //含有 ioremap 函数 iounmap 函数
#include <asm/uaccess.h> //含有 copy_from_user 函数和含有 copy_to_user 函数
#include <linux/device.h> //含有类相关的设备函数
#include <linux/cdev.h>
#include <linux/platform_device.h> //包含 platform 函数
#include <linux/of.h> //包含设备树相关函数
#include <linux/fb.h> //包含 frame buffer
#include <linux/spi/spi.h>
#include <linux/regmap.h>
#include <linux/gpio.h>static struct fb_info *myfb;                 //定义一个fb_info
static struct task_struct *fb_thread;        //定义一个线程刷新屏幕
static struct spi_device *fbspi;             //保存spi驱动指针
static struct gpio_desc *dc_pin;             //dc引脚
static struct gpio_desc *reset_pin;          //reset引脚
static u32    pseudo_palette[16];            //调色板缓存区/*
@function : write an 8-bit command to the LCD screen
@param : reg: comnmand value to be written
@return : none
*/
static void fb_write_reg(struct spi_device *spi, u8 reg)
{gpiod_set_value(dc_pin, 0);              //低电平,命令spi_write(spi, &reg, 1);}/*
@function : write an 16-bit data into registers,rgb565->rgb666
@param : data:data to be written
@return : none
*/
static void fb_write_data_u16(struct spi_device *spi, u16 data)
{u8 buf[3];buf[0] = ((u8)(data>>8))&0xF8;  //REDbuf[1] = ((u8)(data>>3))&0xFC;  //GREENbuf[2] = ((u8)(data<<3));       //BLUEgpiod_set_value(dc_pin, 1); //高电平,数据spi_write(spi, &buf[0], 1);spi_write(spi, &buf[1], 1);spi_write(spi, &buf[2], 1);
}/*
@function :write an 8-bit data to the lcd screen
@param :data:data value to written
@return : none
*/
static void fb_write_data_u8(struct spi_device *spi, u8 data)
{gpiod_set_value(dc_pin, 1); //高电平,数据spi_write(spi, &data, 1);
}/*
@function :setting lcd display window
@param :xstar: the begining x coordinate of the lcd display windowystar: the begining y coordinate of the lcd display windowxEnd:  the ending x coordinate of the lcd display windowyEnd:  the ending y coordinate of the lcd display window
*/
static void fb_set_win(struct spi_device *spi, u16 xStar, u16 yStar, u16 xEnd, u16 yEnd){fb_write_reg(spi,  0x2a);fb_write_data_u8(spi, xStar >> 8);fb_write_data_u8(spi, xStar & 0xFF);fb_write_data_u8(spi, xEnd >> 8);fb_write_data_u8(spi, xEnd & 0xff);fb_write_reg(spi, 0x2b);fb_write_data_u8(spi, yStar >> 8);fb_write_data_u8(spi, yStar & 8);fb_write_data_u8(spi, yEnd >> 8);fb_write_data_u8(spi, yEnd & 0xff);fb_write_reg(spi, 0x2c);    //  开始写入GRAM,即往lcd的GRAM写数据,LCD随即显示到屏幕上
}static void myfb_init(struct spi_device *spi){gpiod_set_value(reset_pin, 0);  //设置低电平msleep(100);gpiod_set_value(reset_pin, 1);  //设高电平msleep(50);/*   写寄存器,初始化*/fb_write_reg(spi, 0xf7);fb_write_data_u8(spi, 0xa9);fb_write_data_u8(spi, 0x51);fb_write_data_u8(spi, 0x2c);fb_write_data_u8(spi, 0x82);fb_write_reg(spi, 0xc0);fb_write_data_u8(spi, 0x11);fb_write_data_u8(spi, 0x09);fb_write_reg(spi, 0xc1);fb_write_data_u8(spi, 0x41);fb_write_reg(spi, 0xc5);fb_write_data_u8(spi, 0x00);fb_write_data_u8(spi, 0x0a);fb_write_data_u8(spi, 0x80);fb_write_reg(spi, 0xb1);fb_write_data_u8(spi, 0xB0);fb_write_data_u8(spi, 0x11);fb_write_reg(spi, 0xb4);fb_write_data_u8(spi, 0x02);fb_write_reg(spi, 0xb6);fb_write_data_u8(spi, 0x02);fb_write_data_u8(spi, 0x42);fb_write_reg(spi, 0xb7);fb_write_data_u8(spi, 0xc6);fb_write_reg(spi, 0xbe);fb_write_data_u8(spi, 0x00);fb_write_data_u8(spi, 0x04);fb_write_reg(spi, 0xe9);fb_write_data_u8(spi, 0x00);fb_write_reg(spi, 0x36);fb_write_data_u8(spi, (1<<3)|(0<<7)|(1<<6)|(1<<5));fb_write_reg(spi, 0x3a);fb_write_data_u8(spi, 0x66);fb_write_reg(spi, 0xe0);fb_write_data_u8(spi, 0x00);fb_write_data_u8(spi, 0x07);fb_write_data_u8(spi, 0x10);fb_write_data_u8(spi, 0x09);fb_write_data_u8(spi, 0x17);fb_write_data_u8(spi, 0x0b);fb_write_data_u8(spi, 0x41);fb_write_data_u8(spi, 0x89);fb_write_data_u8(spi, 0x4b);fb_write_data_u8(spi, 0x0a);fb_write_data_u8(spi, 0x0c);fb_write_data_u8(spi, 0x0e);fb_write_data_u8(spi, 0x18);fb_write_data_u8(spi, 0x1b);fb_write_data_u8(spi, 0x0f);fb_write_reg(spi, 0xe1);fb_write_data_u8(spi, 0x00);fb_write_data_u8(spi, 0x17);fb_write_data_u8(spi, 0x1a);fb_write_data_u8(spi, 0x04);fb_write_data_u8(spi, 0x0e);fb_write_data_u8(spi, 0x06);fb_write_data_u8(spi, 0x2f);fb_write_data_u8(spi, 0x45);fb_write_data_u8(spi, 0x43);fb_write_data_u8(spi, 0x02);fb_write_data_u8(spi, 0x0a);fb_write_data_u8(spi, 0x09);fb_write_data_u8(spi, 0x32);fb_write_data_u8(spi, 0x36);fb_write_data_u8(spi, 0x0f);fb_write_reg(spi, 0x11);mdelay(50);fb_write_reg(spi, 0x29);mdelay(200);}void fb_refresh(struct fb_info *fbi, struct spi_device *spi){int x, y;u16 *p =(u16 *)(fbi->screen_base );fb_set_win(spi, 0, 0, 479, 319);for (y = 0; y < fbi->var.yres; y++){for (x = 0; x < fbi->var.xres; x++){fb_write_data_u16(spi, *p++);}}
}int thread_func_fb(void *data){struct fb_info *fbi = (struct fb_info *)data;while(1){if(kthread_should_stop())break;fb_refresh(fbi,fbspi);}return 0;
}static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{chan &= 0xffff;chan >>= 16-bf->length;return chan << bf->offset;
}static u32 pseudo_palette[16];static int myfb_setcolreg(u32 regno, u32 red, u32 green, u32 blue, u32 transp, struct fb_info *info){unsigned int val;if (regno > 16)return 1;val = chan_to_field(red, &info->var.red);val |= chan_to_field(green, &info->var.green);val |= chan_to_field(blue, &info->var.blue);pseudo_palette[regno] = val;return 0;
}static struct fb_ops myfb_ops = {.owner        = THIS_MODULE,.fb_write     = fb_sys_write,.fb_setcolreg = myfb_setcolreg,            /* 设置颜色寄存器 */.fb_fillrect  = sys_fillrect,              /* 用像素行填充矩形框,通用库函数 */.fb_copyarea  = sys_copyarea,              /* 将屏幕的一个矩形区域复制到另一个区域,通用库函数*/.fb_imageblit = sys_imageblit,             /* 显示一幅图像,通用库函数*/           };static void myfb_update(struct fb_info *fbi, struct list_head *pagelist){/* 比较粗暴的方式,直接全部刷新 */fbi->fbops->fb_pan_display(&fbi->var,fbi);    /*将应用层数据刷新到FrameBuffer缓存中*/
}static struct fb_deferred_io myfb_defio = {.delay = HZ/20,.deferred_io = &myfb_update,};static struct fb_var_screeninfo myfb_var ={.rotate = 0,.xres   =480,.yres   =320,.xres_virtual =480,.yres_virtual =320,.bits_per_pixel =16,           //每个像素的位数.nonstd    =1,/*rgb565*/.red.offset = 11,.red.length = 5,.green.offset = 5,.green.length =6,.blue.offset = 0,.blue.length = 5,.transp.offset = 0,.transp.length = 0,.activate   =FB_ACTIVATE_NOW,.vmode     =FB_VMODE_NONINTERLACED, 
};static struct fb_fix_screeninfo myfb_fix ={.type =FB_TYPE_PACKED_PIXELS,.visual = FB_VISUAL_TRUECOLOR, //.line_length =480*2,           // width *bpp /8  就是宽度乘每个像素需要的位数除8 ,就是每行需要的字节数.accel = FB_ACCEL_NONE, //没有使用硬件加速.id    = "myfb",
};static int myfb_probe(struct spi_device *spi){int ret;void *gmem_addr;u32 gmem_size;fbspi = spi; //保存spi驱动指针printk(KERN_ERR"register myfb_spi_probe!\n");//申请GPIO用作DC引脚dc_pin = devm_gpiod_get(&spi->dev,"dc",GPIOF_OUT_INIT_LOW);if(IS_ERR(dc_pin)){printk(KERN_ERR"fail to request dc_gpios!\n");}//申请GPIO用作RESET引脚reset_pin =devm_gpiod_get(&spi->dev,"reset",GPIOF_OUT_INIT_HIGH);if(IS_ERR(reset_pin)){printk(KERN_ERR"fail to request reset-gpios!\n");return -1;}gpiod_direction_output(dc_pin, 0);  //设置输出方向gpiod_direction_output(reset_pin, 1); //设置输出方向printk(KERN_INFO"register myfb_probe dev !\n");myfb = framebuffer_alloc(sizeof(struct fb_info), &spi->dev); //向内核申请fb_info结构体//初始化底层操作结构体myfb->fbops =&myfb_ops;  //指定底层操作结构体gmem_size =320*480*2;   //设置显存大小,16bit 占2字节gmem_addr =kmalloc(gmem_size,GFP_KERNEL);  //分配Frame Buffer 显存if(!gmem_addr){printk(KERN_ERR"fail to alloc fb buffer!\n");}myfb->pseudo_palette = pseudo_palette; //假的16色调色板,里面存放了16色的数据,可以通过8bpp数据来找到调色板里面的16色颜色索引值,//模拟出16色颜色来,节省内存,不需要的话,指向一个不用的数组即可myfb->var =myfb_var;   // 设置分辨率参数myfb->fix =myfb_fix;   // 设置显示参数myfb->screen_buffer =gmem_addr; //设置显存地址myfb->screen_size = gmem_size; //设置显存大小myfb->fix.smem_len = gmem_size; //设置应用层显存大小myfb->fix.smem_start =(u32) gmem_addr; //设置应用层数据地址memset((void *)myfb->fix.smem_start, 0,myfb->fix.smem_len);  //清楚数据缓存myfb_init(spi);  //初始化显示屏myfb->fbdefio =&myfb_defio; //设置刷新参数fb_deferred_io_init(myfb);   //初始化刷新机制ret = register_framebuffer(myfb);   //注册fb驱动if(ret){framebuffer_release(myfb);unregister_framebuffer(myfb);devm_gpiod_put(&spi->dev, dc_pin);devm_gpiod_put(&spi->dev, reset_pin);printk(KERN_ERR"fail to register fb dev!\n");return -1;}//开启一个线程用来刷新显示屏fb_thread = kthread_run(thread_func_fb,myfb,spi->modalias);return 0;}int myfb_remove(struct spi_device *spi){fb_deferred_io_cleanup(myfb);  //清除刷新机制unregister_framebuffer(myfb);devm_gpiod_put(&spi->dev,dc_pin);devm_gpiod_put(&spi->dev,reset_pin);return 0;}struct of_device_id myfb_match[] ={{.compatible = "ilitek,ili9488"},{},
};struct spi_driver myfb_drv = {.driver ={.owner = THIS_MODULE,.name = "myfb_spi_driver",.of_match_table = myfb_match,},.probe =myfb_probe,.remove = myfb_remove,} ;module_spi_driver(myfb_drv);MODULE_LICENSE("GPL"); //不加的话加载会有错误提醒
MODULE_AUTHOR("2241507913@qq.com"); //作者
MODULE_VERSION("0.1"); //版本
MODULE_DESCRIPTION("myfb_spi_driver"); //简单的描述

设备树修改

在bcm2711-rpi-4-b.dts设备树修改&spi0

&spi0 {pinctrl-names = "default";pinctrl-0 = <&spi0_pins &spi0_cs_pins>;cs-gpios = <&gpio 8 1>, <&gpio 7 1>;status = "okay";ili9488@0{status = "okay";compatible = "ilitek,ili9488";reg = <0>;  /* CE0 Ƭѡ*/#address-cells = <1>;#size-cells = <0>;spi-max-frequency = <32000000>;dc-gpios = <&gpio 17 GPIO_ACTIVE_HIGH>;reset-gpios = <&gpio 18 GPIO_ACTIVE_HIGH>;rotation =<90>;};spidev1: spidev@1{compatible = "spidev";reg = <1>;	/* CE1 */#address-cells = <1>;#size-cells = <0>;spi-max-frequency = <125000000>;};
};

测试

使用insmod加载ili9488.ko
在这里插入图片描述

加载完成后,在/dev目录下可以看到fb0,此为驱动里面注册的帧缓冲区
在这里插入图片描述
/dev/tty0是默认映射到/dev/fb0上的,所以,我们通过操作/dev/tty0即可操作屏幕

在这里插入图片描述
解决办法
sudo chmod 777 /dev/tty0
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

ili9488代码分析

  对于调色板myfb_setcolreg()函数,这里并未使用,是直接使用的rgb。
调色板的概念:假如LCD的数据位为16位,那么framebuffer应该每个像素占据16bit,但是为了节省空间,使用8bit表示一个像素,这时候需要引入调色板才能正确传输16位的数据给LCD(正确传输每个像素的数据)。调色板其实就是一片内存,这里面每一格存放16bit的数据。当LCD控制器从framebuffer中取出8bit的数据后,不是直接传给LCD,而是用这个8bit作为索引,从调色板中取出16bit的数据,然后发给LCD。所以,在使用8BPP(每个像素点的位数)格式时,framebuffer中存放的是伪彩色,16BPP或者24BPP格式时,framebuffer中存放的才是真彩色。所以在使用8BPP格式时,首先要设置调色板。
在这里插入图片描述
此部分来源于链接: link

LCD资料下载

链接: [link](链接: link)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/106691.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL视图、用户管理和C语言链接

文章目录 1. 视图1.1 基本使用 2. 用户管理2.1 用户信息2.2 创建用户2.3 修改用户密码2.4 删除用户 3. 数据库的权限3.1 给用户授权3.2 回收权限 4. mysql connect4.1 Connector/C 使用4.2 mysql接口介绍 1. 视图 视图是一个虚拟表&#xff0c;其内容由查询定义。同真实的表一…

百度SEO优化的特点(方式及排名诀窍详解)

百度SEO优化的特点介绍&#xff1a; 百度SEO优化是指对网站进行优化&#xff0c;使其在百度搜索引擎中获得更好的排名&#xff0c;进而获取更多的流量和用户。百度SEO优化的特点是综合性强、效果持久、成本低廉、投资回报高。百度的搜索算法不断更新&#xff0c;所以长期稳定的…

开源任务调度框架

本文主要介绍一下任务调度框架Flowjob的整体结构&#xff0c;以及整体的心路历程。 功能介绍 flowjob主要用于搭建统一的任务调度平台&#xff0c;方便各个业务方进行接入使用。 项目在设计的时候&#xff0c;考虑了扩展性、稳定性、伸缩性等相关问题&#xff0c;可以作为公司…

YOLOv5网络结构图

网络结构图&#xff08;简易版和详细版&#xff09; 网络框架介绍 前言&#xff1a; YOLOv5是一种基于轻量级卷积神经网络&#xff08;CNN&#xff09;的目标检测算法&#xff0c;整体可以分为三个部分&#xff0c; backbone&#xff0c;neck&#xff0c;head。 如上图所示…

C++——多态

多态是有继承关系的类对象调用相同的函数&#xff0c;会有不同的结果。例如&#xff0c;普通人买高铁票不打折&#xff0c;学生打75折&#xff0c;儿童免费&#xff0c;这种情况就适合使用多态 虚函数 被virtual修饰的类成员函数 class A{ public:virtual void fun() {} }; …

【深度学习】深度学习实验四——循环神经网络(RNN)、dataloader、长短期记忆网络(LSTM)、门控循环单元(GRU)、超参数对比

一、实验内容 实验内容包含要进行什么实验,实验的目的是什么,实验用到的算法及其原理的简单介绍。 1.1 循环神经网络 (1)理解序列数据处理方法,补全面向对象编程中的缺失代码,并使用torch自带数据工具将数据封装为dataloader。 (2)分别采用手动方式以及调用接口方式…

Redis--List、Set、Zset、Hash、Bitmaps、HyperLogLog、Geospatial

List LPUSH key value1 [value2] 将一个或多个值插入到列表头部 127.0.0.1:6379> LPUSH myls1 1 (integer) 1 127.0.0.1:6379> LPUSH myls1 2 (integer) 2 127.0.0.1:6379> LRANGE myls1 0 -1 1) "2" 2) "1" LPOP key 移出并获取列表的第一个元素…

【排序算法】详解直接插入排序和希尔排序原理及其性能分析

文章目录 插入排序算法原理细节分析代码实现复杂度分析:稳定性分析:与冒泡排序的对比 希尔排序算法原理细节分析代码实现复杂度分析稳定性分析 总结对比 插入排序 算法原理 插入排序又或者说直接插入排序,是一种和冒泡排序类似的并且比较简单的排序方法&#xff0c; 基本思想…

3、Linux下安装

以下操作仅限于rh系列:支持rpm/yum安装方式&#xff0c;不支持deb/apt安装方式。 以下操作仅限于rh系列&#xff1a;支持rpm/yum安装方式&#xff0c;不支持 deb/apt安装方式。 1、在线下载安装包&#xff1a; wget https://downloads.mysql.com/archives/get/p/23/file/ m…

打造个人专属形象!工业级人物写真生成工具FaceChain开源

简介 FaceChain 是一个可以用来打造个人数字形象的深度学习模型工具。用户仅需要提供最低一张照片即可获得独属于自己的个人形象数字替身。FaceChain 支持在 gradio 的界面中使用模型训练和推理能力&#xff0c;也支持资深开发者使用 python 脚本进行训练推理。 Github链接&…

长短期记忆网络(LSTM)

一. 什么是LSTM Long Short Term Memory&#xff08;LSTM&#xff0c;长短期记忆&#xff09;是一种特殊的递归神经网络。这种网络与一般的前馈神经网络不同&#xff0c;LSTM可以利用时间序列对输入进行分析。 简而言之&#xff0c;当使用前馈神经网络时&#xff0c;神经网络会…

Linux 中如何安全地抹去磁盘数据?

哈喽大家好&#xff0c;我是咸鱼 离过职的小伙伴都知道&#xff0c;离职的时候需要上交公司电脑&#xff0c;但是电脑里面有许多我们的个人信息&#xff08;聊天记录、浏览记录等等&#xff09; 所以我们就需要先把这些信息都删除&#xff0c;确保无法恢复之后才上交 即有些…

MongoDB 索引和常用命令

一、基本常用命令 1.1 案例需求 存放文章评论的数据存放到 MongoDB 中&#xff0c;数据结构参考如下&#xff0c;其中数据库为 articledb&#xff0c; 专栏文章评论 comment 字段名称 字段含义 字段类型 备注 _id ID ObjectId或String Mongo的主键的字段 articlei…

海量数据插入的各种方案试验

海量数据插入的各种方案试验&#xff0c;大家来分析下 前提 源表&#xff1a;一千万条以上记录&#xff0c;54个字段.exp导出文件2.8G以上。 目的表&#xff1a;无索引&#xff0c;空表 试验&#xff1a;A.本地插入。B.跨库插入 初始化 SQL> select count…

怎么团队合作,协作开发

一、代码托管平台 我是在大一下的一个竞赛中接触到的代码托管平台 那个时候我也算是什么都不会的&#xff0c;不过不得不说这个确实比较重要&#xff0c;对我造成了一些冲击 在我看来&#xff0c;代码托管平台的作用就是在一个中转站&#xff08;仓库&#xff09;上存储我们写…

自己在家给电脑重装系统Win10教程

自己在家怎么给电脑重装系统Win10&#xff1f;Win10电脑系统如果操作时间特别长了&#xff0c;就可能出现卡顿、蓝屏等系统问题&#xff0c;这时候用户就想给电脑重装系统&#xff0c;却不知道重装具体的操作步骤&#xff0c;下面小编给大家详细介绍自己在家给电脑重装Win10系统…

IRC/ML:金融智能风控—信贷风控场景简介、两大场景(贷款场景+信用卡场景)、信用卡评分模型设计、反欺诈检测技术的简介、案例应用之详细攻略

IRC/ML:金融智能风控—信贷风控场景简介、两大场景(贷款场景+信用卡场景)、信用卡评分模型设计、反欺诈检测技术的简介、案例应用之详细攻略 目录 信贷风控简介 信贷风控两大场景

Java程序中调用Python脚本(兼容Windows与Linux)

一&#xff0c;说明 想实现如下功能&#xff0c;项目后端架构是Spring Boot&#xff0c;某个功能需要调用Python脚本来实现&#xff0c;调用Python脚本的功能要兼容Windows和Linux&#xff0c;即可以运行在Windows和Linux两种平台上。 二&#xff0c;Java代码 // 定义一个线…

k8s-13 存储之secret

Secret 对象类型用来保存敏感信息&#xff0c;例如密码、OAuth 令牌和 ssh key。 敏感信息放在 secret 中比放在 Pod 的定义或者容器镜像中来说更加安全和灵活 。 Pod 可以用两种方式使用 secret:作为 volume 中的文件被挂载到 pod 中的一个或者多个容器里 当 kubelet 为 pod 拉…

奖学金答辩注意事项

奖学金答辩注意事项 1.PPT制作 注意字数不要太多&#xff0c;一页PPT里面不要堆满了文字。 评审老师主要是来听你演讲和答辩的&#xff0c;而不是来看你的文字叙述的。就算文字有些多&#xff0c;也要尽量以表格的形式呈现&#xff0c;不然看上去就会非常乱。多用图和表&…