尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
IMX6ULL嵌入式Linux下Qt交叉编译与linuxfb部署实战
第一次把 Qt 程序跑在 IMX6ULL 开发板的 LCD 屏幕上时我盯着那个白色窗口愣了好一会儿——不是因为它跑得多流畅而是这整个过程远比我想象中要绕先要交叉编译 tslib 做触摸校准给 Qt 源码配置一大堆构建开关把几百个动态库拷进板子的根文件系统最后还要跟环境变量较劲。这块板子的芯片是单核 Cortex-A7、主频 528MHz、没有 GPU属于典型的低成本 Linux 工控配置但它恰好把嵌入式 Qt 软件渲染的整条链路都摊开给你看。这篇文章就写给准备在 IMX6ULL 这类无 GPU 的 ARM Linux 开发板上认真学 Qt 的人。我默认你会 Linux 基础命令和简单的 C但不需要有交叉编译经验。全文从方案选型、交叉编译、部署运行到踩坑排错都会把当时为什么这样做讲清楚照着操作基本能跑通。1. 动手之前先搞清楚IMX6ULL 上跑 Qt 到底有哪几条路1.1 没有 GPU很多教程里的方案直接失效先给结论IMX6ULL 不带 GPUOpenGL ES、Wayland 合成器这类依赖硬件的方案在它身上没有正常发挥空间。很多桌面 Qt 教程默认你有 X11 或者 Wayland用 QML、用 RHI、做透明窗口特效这些在 IMX6ULL 上要么跑不起来要么性能惨不忍睹。我一开始就是按 PC 思维来结果下载了 Qt Creator 模板工程在板子上跑一个带阴影的 QML 窗口整块屏幕卡成 PPT。所以学习之前先理解你面对的不是“精简版桌面”而是“无显示核心的嵌入式 Linux”。Qt 在嵌入式设备上的显示路径大致分三块内核提供显示缓冲/dev/fb*、上层提供一个 Qt 平台插件platform plugin、中间由 Qt 的软件光栅化完成绘制。IMX6ULL 的常规做法是内核的 LCDIF 控制器输出画面到 RGB LCD驱动暴露一个 FrameBuffer 设备Qt 通过 linuxfb 插件往这个缓冲区里画图。这里就要提到QT_QPA_PLATFORM这是 Qt5 起面向前端平台插件的核心变量。选错插件程序要么报“could not connect to display”要么根本跑不起来。PC 上它默认是xcb嵌入式无 GPU 板卡上默认什么都选不到必须你手动告诉它用linuxfb还是别的后端。1.2 几个可行方案的对比方案核心依赖能否在 IMX6ULL 上运行性能开销适合场景linuxfb内核 FrameBuffer 驱动能最常用最低纯软件绘制工控界面、学习验证、菜单屏EGLFSOpenGL ES / GPU 驱动不能没有 GPU高有 GPU 的全志、i.MX6Dual 等板卡X11/Xorg xcbX Server 显示驱动能但很吃力高内存和 CPU 负担重需要跑桌面级应用的板卡Wayland/WestonWeston GPU 合成基本没有意义高现代嵌入式图形栈没有 GPU 别碰逐个说清楚。linuxfb 是 Qt5 里专门为 FrameBuffer 设备设计的平台插件它不需要 X Server也不需要 Wayland直接把绘制结果写到/dev/fb0。它是 IMX6ULL 的最小可行方案界面刷新、事件接收都在这个插件里完成逻辑简单排查问题也容易。EGLFS 在 Qt 官方文档里常被推荐给“嵌入式 Linux”使用但它默认依赖 OpenGL ES 和 GPU 驱动。IMX6ULL 芯片内部没有图形核心硬件上就不满足条件。网上有些方案试图用软件渲染模拟 EGL老实说学习阶段没必要碰这种旁门左道收益很低还容易把环境搞乱。X11/Xorg 也曾在某些教程里出现过在板子上启动一个轻量 X Server然后 Qt 用 xcb 连接。这种方案能跑但 X Server 本身要占不少内存而且每个窗口都要走一次 X 协议转发性能损耗大。IMX6ULL 这类内存和 CPU 都紧张的低成本板子跑桌面级应用本来就是强人所难学习 Qt 没必要给自己叠这么多层。1.3 其实你还可以先不用开发板一个小提醒学习 Qt 本身最好先在 PC 上写例子。等界面代码逻辑没问题再走交叉编译到板子。否则 PC 的窗口和板子上的 FrameBuffer 两者混在一起出了问题很难分清是界面代码的锅还是环境配置的锅。很多教程一上来就让你在板子上跑小例子反而容易劝退。我这篇的路线是PC 上准备一个小型 QWidget 工程一个按钮、一个文本框、一个 label 就够确认能编译运行然后把它作为“测试饵”后面每次环境改动都拿它验证。这个习惯能帮你省下大量排错时间后面讲到实机运行时会反复用到这个测试程序。2. 交叉编译环境的搭建与依赖处理2.1 交叉编译器版本配对比速度重要目标设备是 ARM 架构所以你需要一套能在 PC 上运行、但生成 ARM 机器码的交叉编译器。IMX6ULL 常用的是arm-linux-gnueabihf工具链。这里有个非常容易踩的坑交叉编译器的 glibc/libstdc 版本必须和板子根文件系统里的版本匹配。我见过不少例子Qt 交叉编译一路顺利拷到板子上运行却直接报./qt程序: /lib/arm-linux-gnueabihf/libstdc.so.6: version GLIBCXX_3.4.x not found。这就是编译器版本太新、而板子上的动态库版本太旧导致的。选工具链时优先用开发板厂商随板卡提供的交叉编译器或者和板子 rootfs 同源的交叉编译器自己随便下载一个最新版 gcc 反而容易给自己挖坑。确认匹配可以先看编译器版本arm-linux-gnueabihf-gcc -v再看板子上的库版本strings /usr/lib/arm-linux-gnueabihf/libstdc.so.6 | grep GLIBCXX两边能查到的最大版本号大致吻合基本就没问题。我自己的习惯是第一次工具链到位后先交叉编译一个空的 hello world 放进板子跑通再开始搞 Qt避免后面环境错乱。2.2 tslib先在主机上搞定触摸屏驱动依赖触摸屏输入不能直接靠 Qt 的桌面鼠标路径。IMX6ULL 常用的是电阻屏或电容屏内核会把触摸上报成/dev/input/eventX节点但这个节点给的是设备坐标不是屏幕像素坐标而且原始数据有抖动。tslib 就是干这个的它从 input 子系统读原始触控数据做滤波、去抖、校准最后输出能对应到屏幕像素的坐标。先交叉编译 tslib# 从官方仓库拉取 tslib 源码后 ./autogen.sh ./configure --hostarm-linux-gnueabihf --prefix/opt/tslib CCarm-linux-gnueabihf-gcc make -j4 make install常见问题autogen 阶段报缺少 libtool可以先手动执行libtoolize --force再重新 autogenconfigure 阶段卡在 X11 相关检查加--without-x11跳过。交叉编译出的 tslib 会被安装在/opt/tslib里面有include/tslib.h、lib/libts.so、bin/ts_calibrate等后面 Qt 的 configure 和板子上运行都要用到。之所以先把 tslib 折腾好是因为 Qt 的 linuxfb 平台插件编译时如果检测不到tslib.h和libts.so它只是默默地在 configure 日志里写一句“tslib support cannot be enabled”不会直接让你编译失败结果就是你辛辛苦苦把 Qt 编完板子上怎么点都没反应。这个坑藏得很深所以顺序上必须先做 tslib。2.3 准备 Qt 源码和 configure 思路要交叉编译 Qt需要下载 Qt5 的源码包。这里强烈建议用 Qt 5.15 分支它是最后一条支持 linuxfb 且资料成熟的路线。Qt6 虽然也还活着但构建系统、平台插件组织方式改动很大嵌入式无 GPU 场景下相关经验少学习阶段没必要替它趟路。拿到 qtbase 源码后核心工作就是./configure。这个阶段没必要把 Qt 所有模块都编一遍。对于在 IMX6ULL 上学习 Widget 程序设计几乎只用得到 core、gui、widgets 三个模块其余的网络、SQL、多媒体、WebEngine 都可以关掉后面讲到体积裁剪时还会再提。configure 的关键开关可以分为两类一类是“必须开的”比如-prefix /opt/qt5.15指定 Qt 的安装路径后面整个目录要拷到板子上。-xplatform linux-arm-gnueabihf-g告诉 Qt 使用 ARM 交叉编译的 mkspec这是交叉编译的核心开关。-linuxfb启用 linuxfb 平台插件这是无 GPU 板卡的显示后端。-tslib启用 tslib 触摸支持同时通过-I和-L指定 tslib 的头文件和库文件路径。另一类是“必须关的”比如-no-opengl没有 GPUOpenGL 相关代码只会带来编译依赖和运行风险。-no-eglfs -no-wayland -no-xcb这些平台后端在无 GPU 板卡上要么用不了要么徒增复杂度。-no-gstreamer -no-webengine -no-gamepad关掉重模块能显著缩短编译时间。-nomake examples -nomake tests -nomake benchmarks不生成示例和测试程序否则多编几个小时。图片解码方面建议用-qt-zlib -qt-libpng -qt-libjpeg让 Qt 自带这些库而不是依赖板子文件系统里可能缺失的版本。3. Qt 源码编译的完整流程与裁剪思路3.1 一份可以照着用的完整 configure 命令我自己在 IMX6ULL 上的实际配置如下注释写在每一行后面./configure \ -prefix /opt/qt5.15 \ -opensource -confirm-license \ -release \ -xplatform linux-arm-gnueabihf-g \ -linuxfb \ -tslib \ -I/opt/tslib/include -L/opt/tslib/lib \ -no-opengl \ -no-eglfs -no-wayland -no-xcb -no-xkbcommon \ -qt-zlib -qt-libpng -qt-libjpeg \ -no-sql-mysql -no-sql-sqlite \ -no-gstreamer -no-webengine -no-gamepad \ -nomake examples -nomake tests -nomake benchmarks解释几个容易理解错的点。-release不带 debug 符号编译产物小、运行效率高非常适合嵌入式。学习阶段想调试可以用-release -force-debug-info这种折中配置但默认先把-release跑通更简单。-no-xkbcommon可能让一些从 PC 习惯过来的人陌生。xkbcommon 负责键盘布局如果板子没有实体键盘、只靠触摸操作关掉它减少一层依赖。如果后面突然需要 USB 键盘输入再回去打开重编也不迟但学习阶段通常用不到。-nomake examples特别重要。Qt5 的示例工程数量巨大不关的话会消耗大量交叉编译时间。我第一次没关一下午全耗在 examples 编译上最后这些示例根本不会用到。运行 configure 后如果看到类似tslib support enabled和linuxfb enabled的提示就可以继续make -j4 make install编译耗时取决于 CPU主机性能好一点大概二三十分钟差一点一小时也不意外。装完之后/opt/qt5.15目录里会生成一个完整的 Qt 运行时环境。3.2 编译出来之后先检查什么make install完成别急着拷贝先检查几个关键产物ls -l /opt/qt5.15/bin/qmake ls -l /opt/qt5.15/plugins/platforms/ file /opt/qt5.15/lib/libQt5Core.so.5重点看plugins/platforms/下有没有libqlinuxfb.so。这个文件就是 linuxfb 平台插件没有它板子上永远启动不了程序。同时用file命令确认产物是 ARM 格式别一不小心编出 x86 版本来。这里还有一个很多新手会困惑的点宿主机的qmake到底是给谁用的。configure 生成的bin/qmake需要在 PC 上运行它的作用是解析你程序里的.pro工程文件生成适合目标平台的 Makefile。因为 configure 时已经指定了交叉平台所以它生成的 Makefile 里 CC、CXX、CFLAGS 都指向 ARM 交叉编译器。你拿到这个 qmake在 PC 上对你的工程执行qmake make出来的可执行文件就是 ARM 版本拷到板子上就能跑。这个“主机的 qmake 交叉的 Makefile”机制理解清楚后面写自己的工程就不会迷糊。3.3 体积裁剪能省则省Qt 的安装目录默认还是很大的不裁剪直接塞进板子对存储是很大压力。按我的经验精简后一般能控制在 50~100MB具体看开了多少模块。主要手段是这几个只拷贝/opt/qt5.15/lib下实际用到的.so。QtWidgets、QtGui、QtCore 必留QtNetwork 等项目没用到就可以不放。plugins/platforms/只留libqlinuxfb.so其他的平台插件全部删掉。plugins/imageformats/只留libqjpeg.so和libqpng.so其他图片格式按需决定。用arm-linux-gnueabihf-strip对动态库做一次瘦身能去掉大量调试符号。命令大概是arm-linux-gnueabihf-strip /opt/qt5.15/lib/libQt5*.so.5.15.*别小看这一步strip 之后整个库体积能缩小三分之一还多。不过注意别对qmake这类主机工具做 strip否则它可能在 PC 上跑不了。4. 部署到开发板文件、环境变量一个都不能少4.1 把 Qt 和 tslib 拷进板子开发调试阶段比起反复往 SD 卡/emmc 里拷贝文件我强烈建议先用 NFS 挂载。PC 上把/opt分享出来板子上启动时挂载过去改动代码、重编 Qt 插件都能即时生效不用一次次拔卡。板子上执行mount -t nfs 主机IP:/srv/nfs/qtroot /opt -o nolock挂载成功后/opt/qt5.15和/opt/tslib就直接出现在板子上。等调试稳定了再把这些目录拷贝到根文件系统的实际路径里开机脚本自动加载。如果不想用 NFS直接 scp 拷贝也可以但要注意板子 flash 或 SD 卡的剩余空间。Qt 库文件很多拷贝时最好用 tar 打包传过去再解压避免大量小文件传输出错。4.2 环境变量配置清单与原理在板子的/etc/profile里加一段环境变量每次登录自动生效export QTDIR/opt/qt5.15 export LD_LIBRARY_PATH/opt/qt5.15/lib:/opt/tslib/lib:$LD_LIBRARY_PATH export QT_QPA_PLATFORMlinuxfb export QT_QPA_FB_TSLIB1 export QT_QPA_FONTDIR/opt/qt5.15/lib/fonts export TSLIB_ROOT/opt/tslib export TSLIB_FBDEVICE/dev/fb0 export TSLIB_TSDEVICE/dev/input/event1 export TSLIB_CALIBFILE/etc/pointercal export TSLIB_CONFFILE/opt/tslib/etc/ts.conf逐项说明LD_LIBRARY_PATH是重中之重程序启动时动态链接器找不到 Qt 库第一反应就是去这里找。顺序上把 Qt 的 lib 放在最前面避免板子原有路径里同名的旧库覆盖 Qt 的库。QT_QPA_PLATFORM就是前面说的平台插件选择必须指定linuxfb。QT_QPA_FB_TSLIB1是让 linuxfb 插件从 tslib 拿触摸坐标。没有这一项触摸数据不会走 tslib 的校准链路点击位置会偏移。QT_QPA_FONTDIR后面单独讲字体坑很大。TSLIB_TSDEVICE要特别注意它指向触摸屏对应的 input 事件节点。不同板子上 eventX 编号不一样怎么确认用cat /proc/bus/input/devices看 Handlers 列写了eventX的那个设备再结合 name 判断是不是触摸屏。不要凭感觉写死很多触摸失灵的问题就出在这个节点写错了。TSLIB_CALIBFILE是校准文件路径下面马上讲到。4.3 触摸校准与验证配置完环境变量先启动 ts_calibratets_calibrate屏幕会依次出现几个十字光标依次点击后生成/etc/pointercal文件。这个文件记录了触摸坐标到屏幕坐标的变换参数Qt 通过QT_QPA_FB_TSLIB1读取并应用所以乱写路径导致 Qt 认为没有校准文件时界面上按钮点了没反应或点到别的地方。校准完可以用ts_test随手划一划屏幕上的光标应该跟着手指走且位置基本吻合。此时再跑 Qt 测试程序./helloworld -platform linuxfb:fb/dev/fb0 看到窗口浮在 LCD 上手指点按钮有响应说明整条链路已经通了。这个测试程序就是开头提到的“测试饵”后面任何改动都用它验证能立刻定位问题。5. 实机运行第一次点亮窗口之后的踩坑记录5.1 中文和字体显示问题界面跑起来之后最典型的问题就是中文变成方块也就是俗称的“豆腐块”。原因不复杂Qt 默认字体是西文字体比如 DejaVu Sans它里面没有中文字形遇到中文字符只能显示空白或方框。解决办法是找一套包含 CJK 字符集的开源字体文件拷到开发板的字体目录然后让 Qt 认识它。拷贝到/opt/qt5.15/lib/fonts环境变量里已经设置了QT_QPA_FONTDIR指向这里Qt 启动时会自动扫描这个目录。代码里显式指定字体QFont font; font.setFamily(你的字体名); font.setPixelSize(24); QApplication::setFont(font);字体名不一定是文件名要用字体内嵌的 family 名称可以在 PC 上打开字体属性查看。如果代码里指定的 family 找不到Qt 会悄悄回退到默认字体界面仍然是一堆方块。这时候可以用一个简单标签页把每个可用字体名打出来再逐个试虽然土但有效。另外如果程序里用的是 GBK 编码的字符串在 Qt5 里默认按 UTF-8 解释会直接乱码。建议所有源码文件统一 UTF-8字符串用中文时尽量在代码顶部加#include QString配合QStringLiteral避免字符集问题干扰判断。5.2 刷新闪烁、卡顿和 CPU 占用过高IMX6ULL 的主频只有 528MHz没有 GPUQt 所有绘制都靠 CPU 软渲染。如果界面代码写得花哨卡顿几乎是必然的。我自己实测下来简单的 QWidget 界面空闲 CPU 占用基本能控制在 20% 以下但如果你开始上大量透明效果、阴影、高分辨率 png、全局样式表CPU 占用能直接冲到 90% 以上。几条实用的优化经验不要使用Qt::WA_TranslucentBackground窗口透明会强制整个窗体重绘成本极高。不要用全局setStyleSheet做复杂样式每改动一次所有子控件都会重新求值和重绘。图片显示尽量用QPixmap提前加载到内存不要在paintEvent里每次现做QImage::load。需要刷新界面时只对变化的区域调用update()不要整个窗口update()。劳模式QTimer定时器周期别设太短尤其是 10ms 级别的周期在无 GPU 板子上就是原地自爆。闪烁问题有时候也来自 framebuffer 本身。linuxfb 后端默认有一层后备缓冲避免撕裂但如果屏幕出现整体闪烁可以去检查设备树里 LCD 时序参数或者试试在启动参数里加QT_QPA_FB_NO_BACKSTORE1让 Qt 直接写屏牺牲一点撕裂换取速度。这个选项不建议一开始就加先加界面优化看效果再动平台层。5.3 排错检查清单把踩过的坑整理成一张表以后遇到问题先对着查现象定位方向启动报could not connect to displayQT_QPA_PLATFORM没设对 / linuxfb 插件缺失缺失启动白屏或黑屏无响应/dev/fb0是否存在、当前用户有无权限、LCD 时序有没有初始化好按钮点击没反应或点击位置偏移tslib 环境变量 /TSLIB_TSDEVICE节点写错 /pointercal不存在校验失败中文字体显示方块字体目录缺 CJK 字体 /QT_QPA_FONTDIR没生效 / 代码里 family 写错运行直接报 GLIBCXX 版本交叉编译器和板子 rootfs 版本不匹配换工具链解决鼠标箭头乱跑触摸事件被当成鼠标处理确认QT_QPA_FB_TSLIB1是否正确生效事件节点有没有混用界面卡顿 CPU 高检查是否有定时器频繁触发、窗口特效、全量重绘、复杂样式表等问题这几类问题覆盖了我在 IMX6ULL 上从头折腾到跑通界面时遇到的绝大多数。每一条背后都对应一次漫长的排查过程当时要是有一张这样的表真能省掉一半时间。顺带补充一个很容易忽略的点板子上执行 Qt 程序之前先确认触摸事件节点没有变化。有些系统每次开机会重新枚举 input 设备eventX编号不稳定。我后来学乖了启动脚本里用/dev/input/eventX前会先用 shell 检查/proc/bus/input/devices里的名称匹配而不是写死编号。回头来看在 IMX6ULL 上跑 Qt真正的门槛不是 Qt 本身而是整个嵌入式图形链路的底层逻辑内核的 framebuffer、tslib 的数据处理、平台插件的选择、环境变量的串联。这些概念在 PC 上开发时完全接触不到但正是它们构成了嵌入式 Qt 的价值所在。等你在板子上亲手点亮一个窗口并且触摸点击顺滑之后再去看其他显示方案、或者换到别的板子都能快速上手。
RELATED

相关推荐

静安废电路板回收:你以为的垃圾,其实是座矿

静安废电路板回收:你以为的垃圾,其实是座矿

你丢掉的电路板,其实是座小矿 在上海静安区这个地方, 当不少人在更换了新的手机设备或者是淘汰了旧的计算机之后, 对于所产生的主板以及电路板等物品, 存在着一种随意的丢弃行为, 这种做法其实是大家所存在的一个大的认知误区, 要知道废弃的电路板它根本不属于普通的…

📅 2026/10/15 17:13:41
Webots Billboard 节点完全指南:让 HUD 信息永远面向视点

Webots Billboard 节点完全指南:让 HUD 信息永远面向视点

科研自动驾驶物理引擎 【免费下载链接】webots Webots Robot Simulator 项目地址: https://gitcode.com/gh_mirrors/web/webots 点击查看 免费下载 导读 本文讲解 Webots 仿真器中的 Billboard(公告板)节点:它能让一组子节点在三…

📅 2026/10/15 17:13:41
esp-adf adf_event_hub 测试工程全解析:PC 与 ESP 双目标统一测试、覆盖率与基准实践

esp-adf adf_event_hub 测试工程全解析:PC 与 ESP 双目标统一测试、覆盖率与基准实践

音视频音频处理嵌入式物联网语音 【免费下载链接】esp-adf Espressif Advanced Development Framework for Multimedia Applications 项目地址: https://gitcode.com/gh_mirrors/es/esp-adf 点击查看 免费下载 本文围绕 esp-adf 仓库中 adf_event_hub 测试工程文档…

📅 2026/10/15 17:13:41
MORE NEWS

更多资讯

📰

用 Swift 和 SpriteKit 打造可二次开发的 Flappy Bird 工程样板

简介:面向iOS游戏开发初学者的Flappy Bird Xcode工程源码包,基于SpriteKit框架用Objective-C编写,可直接用Xcode打开编译,完整呈现经典小游戏从物理模拟到交互响应的开发链路。压缩包共58个文件、约507KB,内容以Object…

📰

脑机接口(BCI)硬核对决:NextMind 9通道多电极 vs 脑智科技 VisionBCI 单通道干电极

近年来,非侵入式脑机接口(BCI)技术逐渐从实验室走向公众视野。在众多技术路线中,基于稳态视觉诱发电位(SSVEP, Steady-State Visually Evoked Potential)的方案因其无需漫长的意念训练、即戴即用的特点&…

📰

MATLAB实战FP-Growth关联规则挖掘:从建树到规则生成

简介:这份资源是面向数据挖掘初学者与MATLAB使用者的FP-Growth关联规则挖掘实现包,用于在交易数据中高效发现频繁项集并提取关联规则,适合市场篮子分析、网络行为分析等场景的入门实践。压缩包共8个文件,以7个.m脚本和1个.mat数据…

📰

大模型算法岗三面面试真题复盘:从Transformer到DeepSpeed分片

简介:这份资料整理了字节跳动大模型算法岗位一面、二面、三面的完整面试题目,面向准备冲击大厂算法岗的求职者,尤其是已有一定深度学习基础、希望系统梳理大模型知识体系的中高级候选人。内容覆盖Transformer与LLaMA的LN差异、RMSNorm与FFN结…

📰

ARMCC编译器实战:Cortex-M老工程选型、编译与避坑

简介:ARM Compiler Version 5(ARMCC)是ARM官方推出的32位ARM架构编译工具链,面向嵌入式开发者、单片机工程师及需要针对Cortex-A/R/M系列进行底层优化的技术人员。它整合了编译器、汇编器、链接器与调试组件,支持C/C源…

📰

外贸主动获客工具大比拼:海关数据、社媒爬虫插件与 AI 工作台,谁才是成单之王?

在外贸圈,有一句老话:“坐等询盘是等死,主动出击才有肉吃。” 为了帮团队主动开发海外客户,外贸老板们几乎把市面上的获客工具试了个遍:从几千块一年的海关数据查询网,到几百块的谷歌地图/领英邮箱抓取插件…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬