STM32CubeMX学习笔记18——FSMC(TFT-LCD屏触摸)

news2024/10/5 13:15:23

1.触摸屏简介

目前最常用的触摸屏有两种:电阻式触摸屏和电容式触摸屏

1.1 电阻式触摸屏

电阻式的触摸屏结构如下图示,它主要由表面硬涂层、两个ITO层、间隔点以及玻璃底层构成,这些结构层都是透明的,整个触摸屏覆盖在液晶面板上,透过触摸屏可看到液晶面板。表面涂层起到保护作用,玻璃底层起承载的作用,而两个ITO层是触摸屏的关键结构,它们是涂有铟锡金属氧化物的导电层。两个ITO层之间使用间隔点使两层分开,当触摸屏表面受到压力时,表面弯曲使得上层ITO与下层ITO接触,在触点处连通电路。

两个ITO涂层的两端分别引出X-、X+、Y-、Y+四个电极,这是电阻屏最常见的四线结构,通过这些电极,外部电路向这两个涂层可以施加匀强电场或检测电压。

当触摸屏被按下时,两个 ITO 层相互接触,从触点处把 ITO 层分为两个电阻,且由于 ITO 层均匀导电,两个电阻的大小与触点离两电极的距离成比例关系,利用这个特性,可通过以下过程来检测坐标,这也正是电阻触摸屏名称的由来。

 计算 X 坐标时,在 X+ 电极施加驱动电压 Vref,X-极接地,所以 X+ 与 X-处形成了匀强电场,而触点处的电压通过 Y+ 电极采集得到,由于 ITO 层均匀导电,触点电压与 Vref 之比等于触点 X 坐标与屏宽度之比,从而:

计算 Y 坐标时,在 Y+ 电极施加驱动电压 Vref,Y-极接地,所以 Y+ 与 Y-处形成了匀强电场,而触点处的电压通过 X+ 电极采集得到,由于 ITO 层均匀导电,触点电压与 Vref 之比等于触点 Y 坐标与屏高度之比,从而:

电阻触摸屏的优缺点

- 优点:精度高、价格便宜、抗干扰能力强、稳定性好
- 缺点:容易被划伤、透光性不太好、不支持多点触控

触摸屏都需要一个AD转换器来将电压变化读取出来,供主机算出触摸的位置。本例程中的TFTLCD模块使用的是四线电阻式触摸屏,触摸屏控制芯片为XPT2046,XPT2046是一款4导线制触摸屏控制器,采用SPI模式进行通讯,内含12位分辨率125KHz转换速率逐步逼近型A/D转换器。XPT2046引脚图(SOP-16封装)及引脚说明如下图示

1.2 电容式触摸屏

与电阻式触摸屏不同,电容式触摸屏不需要通过压力使触点变形。它的基本原理是利用充电时间检测电容大小,若手指触摸屏幕,会影响触摸点附近两个电极之间的耦合,从而改变两个电极之间的电容量,若检测到某电容的电容量发生了改变,即可获知该电容处有触摸动作从而通过检测出电容值的变化来获知触摸信号。

由于本例程使用的是电阻式触摸屏,这里电容式触摸屏不做详细介绍

FSMC简介在这里不再赘述,有需要请查看STM32CubeMX学习笔记17--- FSMC-CSDN博客

2、 硬件设计

led2指示灯用来提示系统运行状态,s1按键用来强制校准电阻触摸屏(电容屏无需校准),AT24C02用来存储电阻触摸屏校准数据,TFTLCD模块用来显示触摸

  • LED1指示灯
  • S1按键
  • TFTLCD模块
  • AT24C02
  • USART1

3、 STM32CubeMX设置 

  • RCC设置外接HSE,时钟设置为72M
  • PC0设置为GPIO推挽输出模式、上拉、高速、默认输出电平为高电平
  • PA0设置为GPIO输入模式、下拉模式
  • USART1选择为异步通讯方式,波特率设置为115200Bits/s,传输数据长度为8Bit,无奇偶校验,1位停止位
  • 激活FSMC,详细请参考STM32CubeMX学习笔记17--- FSMC-CSDN博客的设置
模拟SPI

  • 由于开发板上画的是普通的IO口,所以这里只能用模拟SPI。实际项目可使用硬件SPI接口。

在 System Core 中选择 GPIO 设置。

配置以下 5 个引脚:

  • PB1设置为GPIO推挽输出模式、浮空、高速、默认输出电平为低电平,重命名为:XPT2046_SPI_CS
  • PB2设置为GPIO推挽输出模式、浮空、高速、默认输出电平为高电平,重命名为:XPT2046_SPI_CLK
  • PF9设置为GPIO推挽输出模式、浮空、高速、默认输出电平为低电平,重命名为:XPT2046_SPI_MOSI
  • PF8设置为GPIO推挽输入模式、上拉,重命名为:XPT2046_SPI_MISO
  • PF10设置为GPIO推挽输入模式、上拉,重命名为:XPT2046_SPI_PENIRQ 笔接触中断引脚

 ​​​​

输入工程名,选择工程路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码

4、程序编程

修改代码优化级别

        STM32CubeMX生成的代码默认优化级别为Level 3,在此优化级别下编译无误下载到开发板后,LCD屏不能正常运行;将优化级别调整到Level 0编译下载后,LCD屏能够正常运行读取到ID。

KEIL5中C/C++优化等级介绍:
-O0:最少的优化,可以最大程度上配合产生代码调试信息,可以在任何代码行打断点,特别是死代码处。
-O1:有限的优化,去除无用的inline和无用的static函数、死代码消除等,在影响到调试信息的地方均不进行优化。在适当的代码体积和充分的调试之间平衡,代码编写阶段最常用的优化等级。
-O2:高度优化,调试信息不友好,有可能会修改代码和函数调用执行流程,自动对函数进行内联等。
-O3:最大程度优化,产生极少量的调试信息。会进行更多代码优化,例如循环展开,更激进的函数内联等。

  • 添加按键驱动文件key.c和key.h
  • 添加AT24C02驱动文件24cxx.c和24cxx.h
  • 添加TFTLCD驱动文件tftlcd.c 和tftlcd.h,参考FSMC例程
  • 添加XPT2046触摸芯片驱动文件touch.c和touch.h 两个重要的结构体:
typedef struct{     /*存储触点读取到的数据*/
    uint16_t x;     //x轴物理坐标值
    uint16_t y;     //y轴物理坐标值
    uint16_t lcdx;  //x轴彩屏坐标值
    uint16_t lcdy;  //y轴彩屏坐标值
}TouchTypeDef;

typedef struct{     /*保存校正因素*/
  uint8_t posState; //校正参数标志
  int16_t xOffset;  //x轴偏移量
  int16_t yOffset;  //y轴偏移量
  float   xFactor;  //x轴比例因数
  float   yFactor;  //y轴比例因数
}PosTypeDef;
触摸屏初始化及读取物理坐标(ADC值)函数:
void TOUCH_Init(void){
    //读取AT24C02的TOUCH_ADJ_ADDR地址处的值
    HAL_I2C_Mem_Read(&hi2c2,ADDR_24CXX_READ,TOUCH_ADJ_ADDR,I2C_MEMADD_SIZE_8BIT,&TouchAdj.posState,sizeof(TouchAdj),0xff);  
    if(TouchAdj.posState != TOUCH_ADJ_OK){  //检查是否有校正数据
        TOUCH_Adjust(); //如果没有校正数据,则启动校正函数  
    }
}
/*封装SPI读取数据函数*/
uint8_t WR_Cmd(uint8_t cmd){
    uint8_t Tx_DATA = cmd;
    uint8_t Rx_DATA = 0;
    HAL_SPI_TransmitReceive(&hspi1,&Tx_DATA,&Rx_DATA,1,500);
    return Rx_DATA;     
}
/*读取X轴或Y轴的ADC值,并进行滤波处理*/
uint16_t TOUCH_Read_AD(uint8_t cmd){
    uint8_t i, j;
    uint16_t NUMH,NUML;
    uint16_t NUM[TOUCH_READ_TIMES] = {0};
    uint16_t temp,value;
    uint32_t totalValue;    //注意数据类型

    for(i=0; i<TOUCH_READ_TIMES; i++){
        TOUCH_CS_LOW(); //开始SPI通讯   
        WR_Cmd(cmd);
        NUMH = WR_Cmd(0XFF);
        NUML = WR_Cmd(0XFF);
        NUM[i] = (NUMH<<8)+ NUML;
        NUM[i] >>= 3;   //最低三位无用    
        TOUCH_CS_HIGH();    //结束SPI通讯
    }
    //滤波处理:1.从大到小排序
    for(i=0; i<(TOUCH_READ_TIMES - 1); i++){
        for(j=i+1; j<TOUCH_READ_TIMES; j++){
            if(NUM[i] < NUM[j]){
                temp = NUM[i];
                NUM[i] = NUM[j];
                NUM[j] = temp;
            }   
        }       
    }
    //滤波处理:2.去掉最大值和最小值,求余下的平均值
    j = TOUCH_READ_TIMES - 1;
    totalValue = 0;
    for(i=1; i<j; i++){
        totalValue += NUM[i];
    }
    value = totalValue/(TOUCH_READ_TIMES - 2);  
    return value;
}
/*读取X轴和Y轴的ADC值,再次进行滤波*/
uint8_t TOUCH_ReadXY(uint16_t *xValue, uint16_t *yValue){   
    uint16_t xValue1, yValue1, xValue2, yValue2;
    xValue1 = TOUCH_Read_AD(TOUCH_X_CMD);
    yValue1 = TOUCH_Read_AD(TOUCH_Y_CMD);
    xValue2 = TOUCH_Read_AD(TOUCH_X_CMD);
    yValue2 = TOUCH_Read_AD(TOUCH_Y_CMD);
    //计算两点之间的采样差值
    if(xValue1 > xValue2)
        *xValue = xValue1 - xValue2;
    else
        *xValue = xValue2 - xValue1;

    if(yValue1 > yValue2)
        *yValue = yValue1 - yValue2;
    else
        *yValue = yValue2 - yValue1;
    //判断采样差值是否在可控范围内
    if((*xValue > TOUCH_MAX+0) || (*yValue > TOUCH_MAX+0))  
        return 0xFF;
    //求平均值
    *xValue = (xValue1 + xValue2) / 2;
    *yValue = (yValue1 + yValue2) / 2;
    //判断得到的值,是否在取指范围内,避免出现飞点现象  
    if((*xValue > TOUCH_X_MAX+0) || (*xValue < TOUCH_X_MIN) || (*yValue > TOUCH_Y_MAX+0) || (*yValue < TOUCH_Y_MIN))                 
        return 0xFF;

    return 0; 
}
触摸屏校准函数:
/*读校准点的物理坐标*/
uint8_t TOUCH_ReadAdjust(uint16_t x, uint16_t y, uint16_t *xValue, uint16_t *yValue){
    uint8_t i;
    uint32_t timeCont;

    LCD_Clear(BACK_COLOR);
    LCD_DrowSign(x, y, RED);    //x和y为指定的校准点的彩屏坐标值
    i = 0;
    while(1){   //读该指定校准点的物理坐标值
        if(!TOUCH_ReadXY(xValue, yValue)){
            i++;
            if(i > 10){
                LCD_DrowSign(x, y, BACK_COLOR);
                return 0;
            }               
        }

        timeCont++;
        if(timeCont > 0xFFFFFFFE){  //超时退出 
            LCD_DrowSign(x, y, BACK_COLOR); 
            return 0xFF;
        } 
    }       
}
/*触摸屏校准函数*/
void TOUCH_Adjust(void){
    uint16_t px[2], py[2], xPot[4], yPot[4];
    float xFactor, yFactor;
    //读取4个校准点的物理坐标值
    if(TOUCH_ReadAdjust(LCD_ADJX_MIN, LCD_ADJY_MIN, &xPot[0], &yPot[0]))
        return;
    HAL_Delay(500);
    if(TOUCH_ReadAdjust(LCD_ADJX_MIN, LCD_ADJY_MAX, &xPot[1], &yPot[1]))
        return;
    HAL_Delay(500);
    if(TOUCH_ReadAdjust(LCD_ADJX_MAX, LCD_ADJY_MIN, &xPot[2], &yPot[2]))
        return;
    HAL_Delay(500);
    if(TOUCH_ReadAdjust(LCD_ADJX_MAX, LCD_ADJY_MAX, &xPot[3], &yPot[3]))
        return; 
    HAL_Delay(500);
    //处理读取到的四个点的数据,整合成对角的两个点
    px[0] = (xPot[0] + xPot[1]) / 2;
    py[0] = (yPot[0] + yPot[2]) / 2;
    px[1] = (xPot[3] + xPot[2]) / 2;
    py[1] = (yPot[3] + yPot[1]) / 2;
    //求出比例因数
    xFactor = (float)LCD_ADJ_X / (px[1] - px[0]);
    yFactor = (float)LCD_ADJ_Y / (py[1] - py[0]);  
    //求出偏移量
    TouchAdj.xOffset = (int16_t)LCD_ADJX_MAX - ((float)px[1] * xFactor);
    TouchAdj.yOffset = (int16_t)LCD_ADJY_MAX - ((float)py[1] * yFactor);
    //将比例因数进行数据处理,并保存
    TouchAdj.xFactor = xFactor ;
    TouchAdj.yFactor = yFactor ;        
    TouchAdj.posState = TOUCH_ADJ_OK;
    //将得出的校正因数保存到AT24C02中的TOUCH_ADJ_ADDR地址处
    HAL_I2C_Mem_Write(&hi2c2,ADDR_24CXX_WRITE,TOUCH_ADJ_ADDR,I2C_MEMADD_SIZE_8BIT,&TouchAdj.posState,sizeof(TouchAdj),0xff);    
}
触摸屏扫描函数:
uint8_t TOUCH_Scan(void){
    if(TOUCH_ReadXY(&TouchData.x, &TouchData.y)) 
        return 0xFF;    //没有触摸,直接返回0xFF
    //根据物理坐标值,计算出彩屏坐标值
    TouchData.lcdx = TouchData.x * TouchAdj.xFactor + TouchAdj.xOffset;
    TouchData.lcdy = TouchData.y * TouchAdj.yFactor + TouchAdj.yOffset;
    //查看彩屏坐标值是否超过彩屏大小
    if(TouchData.lcdx > tftlcd_data.width)
        TouchData.lcdx = tftlcd_data.width;
    if(TouchData.lcdy > tftlcd_data.height)
        TouchData.lcdy = tftlcd_data.height;
    return 0;     
}

修改main.c

加入 ILI9341_Init() LCD屏驱动初始化后,进行 Palette_Init() 绘制触摸画板界面,随后在while循环里面检测触摸 XPT2046_TouchEvenHandler()

nt main(void){
    uint8_t key;
    uint16_t penColor = BLUE;
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_FSMC_Init();
    MX_I2C2_Init();
    MX_SPI1_Init();
    MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
    TFTLCD_Init();
    AT24CXX_Init();
    HAL_Delay(2000);    
    LCD_Clear(WHITE);
    TOUCH_Init();
  /* USER CODE END 2 */
  while (1){
    key=KEY_Scan(0);
    if(key==KEY_UP_PRES){   //按下KEY_UP键进入校准函数
        TOUCH_Adjust(); 
    }       

    if(TOUCH_Scan() == 0){   
        if(TouchData.lcdy > tftlcd_data.height - 18){
            //选择画笔颜色
            if(TouchData.lcdx>220)
                penColor = YELLOW;
            else if(TouchData.lcdx>200)
                penColor = CYAN;  
            else if(TouchData.lcdx>180)
                penColor = GREEN;
            else if(TouchData.lcdx>160)
                penColor = MAGENTA;
            else if(TouchData.lcdx>140)
                penColor = RED;
            else if(TouchData.lcdx>120)
                penColor = BLUE;  
        }else{  //画点
            LCD_Fill(TouchData.lcdx-1, TouchData.lcdy-1, TouchData.lcdx+2,
                TouchData.lcdy+2, penColor);
        }
        //清屏    
        if ((TouchData.lcdx > tftlcd_data.width-8*4) && (TouchData.lcdy < 16))//215 = TFT_XMAX - 24{
            LCD_Fill(0, 0, tftlcd_data.width,tftlcd_data.height-16, BACK_COLOR);
            LCD_ShowString(tftlcd_data.width-8*4,0,tftlcd_data.width,tftlcd_data.height,16,(uint8_t *)"RST");
        }            
    }

    HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);
    HAL_Delay(10);
    }
}

工程代码

使用模拟SPI:链接:百度网盘 请输入提取码 提取码:ugpp

使用标准SPI: 链接:https://pan.baidu.com/s/1fvtCLxaVPn3FIKQzHKR8og 提取码:9pfr 

5、 下载验证

编译无误下载到开发板后,可以看到LED2指示灯不断闪烁,触摸屏显示界面如下图示

6、参考文献

STM32CubeMX学习笔记(39)——FSMC接口使用(TFT-LCD屏触摸)_stm32f207vc cubemx fsmc lcd-CSDN博客

STM32CubeMX系列 | 触摸屏 - 知乎 (zhihu.com) 

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

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

相关文章

45、C++/基础练习20240311

一、提示并输入一个字符串&#xff0c;统计该字符中大写、小写字母个数、数字个数、空格个数以及其他字符个数 要求 使用C风格字符串完成。 代码&#xff1a; #include <iostream>using namespace std;int main() {string buf;//定义字符串类型变量bufcout << &…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Stack容器组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之Stack容器组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Stack容器组件 堆叠容器&#xff0c;子组件按照顺序依次入栈&#xff0c;后一…

软件测试需要学什么?学多久?软件测试技术进阶路线图

很多新手&#xff0c;不知道软件测试学习该如何开始&#xff0c;软件测试需要掌握哪些知识。下面是根据本人的理解&#xff0c;粗略整理的一个学习大纲&#xff0c;基本上涵盖了软件测试工程师需要掌握的全部技能&#xff0c;希望对刚入行或者准备学习测试的朋友提供一点指引。…

哈希表|1.两数之和

力扣题目链接 /*** Note: The returned array must be malloced, assume caller calls free().*/// leetcode 支持 ut_hash 函式庫typedef struct {int key;int value;UT_hash_handle hh; // make this structure hashable} map;map* hashMap NULL;void hashMapAdd(int key, i…

Qt教程 — 1.1 Linux下安装Qt

目录 1 下载Qt 1.1 官方下载 1.2 百度网盘下载 1.3 Linux虚拟机终端下载 2 Qt安装 3 安装相关依赖 4 测试安装 1 下载Qt 1.1 官方下载 通过官网下载对应版本&#xff0c;本文选择的版本为qt-opensource-linux-x64-5.12.12&#xff0c;Qt官方下载链接&#xff1a;htt…

【C++庖丁解牛】模拟实现STL的string容器(最后附源码)

&#x1f4d9; 作者简介 &#xff1a;RO-BERRY &#x1f4d7; 学习方向&#xff1a;致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f4d2; 日后方向 : 偏向于CPP开发以及大数据方向&#xff0c;欢迎各位关注&#xff0c;谢谢各位的支持 目录 1.vs和g下string结构…

P5266 【深基17.例6】学籍管理题解

题目 您要设计一个学籍管理系统&#xff0c;最开始学籍数据是空的&#xff0c;然后该系统能够支持下面的操作&#xff08;不超过条&#xff09;&#xff1a; 插入与修改&#xff0c;格式1 NAME SCORE&#xff1a;在系统中插入姓名为NAME(由字母和数字组成不超过20个字符的字符…

如何使用群晖NAS结合cpolar内网穿透实现公网访问本地Office文件

文章目录 本教程解决的问题是&#xff1a;1. 本地环境配置2. 制作本地分享链接3. 制作公网访问链接4. 公网ip地址访问您的分享相册5. 制作固定公网访问链接 本教程解决的问题是&#xff1a; 1.Word&#xff0c;PPT&#xff0c;Excel等重要文件存在本地环境&#xff0c;如何在编…

智能硬件 | AI手机是营销噱头吗?对哪些行业利好?

趁着AI浪潮&#xff0c;手机厂商争先抢滩AI手机领域&#xff0c;智能手机开始步入AI新时代。AI手机不需要借助第三方App&#xff0c;而是通过手机自身的算力&#xff0c;直接成为用户的智能助手。但手机的AI应用能有多少&#xff0c;是否只是手机厂商为促进销量的营销噱头&…

【框架设计】MVC和MVVM对比图

1. MVC&#xff08;Model-View-Controller&#xff09; 单向通信View和Model通过Controller承上启下 2. MVVM&#xff08;Model-View-ViewModel&#xff09; 数据绑定M -> VM -> V DOM事件监听 V -> VM -> M 1. MVC是单向的&#xff0c;MVVM是双向的&#xff0c;…

CUDA入门之统一内存

原文来自CUDA 编程入门之统一内存 &#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;高性能&#xff08;HPC&#xff09;开发基础教程 &#x1f380;CSDN主页 发狂的小花 &#x1f304;人生秘诀&#xff1a;学习的本质…

Java——正则表达式详解

目录 Java正则表达式1、正则表达式语法1.1、基本的元字符1.2、数量元字符1.3、位置元字符1.4、特殊字符元字符1.5、回溯引用和前后查找1.6、大小写转换1.7、匹配模式 2、Java中的正则表达式2.1、概述2.2、获取匹配位置2.3、捕获组 3、匹配单个字符3.1、匹配纯文本3.2、匹配任意…

小程序对于人力资源行业的创新与变革

随着移动互联网的快速发展&#xff0c;小程序成为了各行各业推广和服务的新利器。对于人力资源行业来说&#xff0c;开发一款定制化的小程序不仅可以提升服务效率&#xff0c;还可以增强品牌形象和用户粘性。那么&#xff0c;如何定制开发人力资源类的小程序呢&#xff1f;下面…

嵌入式3-11

1、整理思维导图 2、尝试学生结构体数组&#xff0c;完成成员的输入&#xff0c;以成绩为条件完成对学生成员的冒泡排序并输出&#xff08;全部在主函数内完成&#xff09; #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.…

【算法】Hash存储——开放寻址法

模拟散列表 维护一个集合&#xff0c;支持如下几种操作&#xff1a; I x&#xff0c;插入一个整数 x&#xff1b; Q x&#xff0c;询问整数 x是否在集合中出现过&#xff1b; 现在要进行 N次操作&#xff0c;对于每个询问操作输出对应的结果。 输入格式 第一行包含整数 N&am…

Retrofit

1.导入依赖 //Retrofit 核心库implementation("com.squareup.retrofit2:retrofit:2.9.0")//响应数据自动序列化//JSONimplementation("com.squareup.retrofit2:converter-gson:2.9.0")//String类型implementation("com.squareup.retrofit2:converter…

Python办公自动化之PDF(二)

Python操作PDF二 1、PyMuPDF简介2、 1、PyMuPDF简介 PyMuPDF&#xff08;也称Fitz&#xff09;开源&#xff0c;提供了一整套用于处理PDF文件的综合工具。使用PyMuPDF&#xff0c;用户可以高效地执行打开PDF、提取文本、图像和表格、操作旋转和裁剪等页面属性、创建新PDF文档以…

详解7道经典指针运算笔试题!

目录 ​编辑 1. 题目一 &#xff08;1&#xff09;代码 &#xff08;2&#xff09;分析 2. 题目二 &#xff08;1&#xff09;代码 &#xff08;2&#xff09;分析 3. 题目三 &#xff08;1&#xff09;代码 &#xff08;2&#xff09;分析 4. 题目四 &#xff08;…

JimuReport积木报表 v1.7.2 版本发布,低代码报表工具

项目介绍 一款免费的数据可视化报表&#xff0c;含报表和大屏设计&#xff0c;像搭建积木一样在线设计报表&#xff01;功能涵盖&#xff0c;数据报表、打印设计、图表报表、大屏设计等&#xff01; Web 版报表设计器&#xff0c;类似于excel操作风格&#xff0c;通过拖拽完成报…