嵌入式界面开发里中文字库这件事看起来不起眼但真到落地阶段十个新手有八个会卡在“屏幕上一堆问号或者方块”这一步。emWin 这套图形库在工业控制、医疗设备、智能家居面板上用得非常多它的字库机制说复杂也不算复杂说简单也绝对不简单——尤其是中文字符数量大、点阵数据占空间、编码方式五花八门稍有不慎就是白屏、乱码、花屏三连击。这篇内容就是围绕“emWin 中文字库显示”这个主题把从字库选型、生成、移植到代码调用的完整链路拆开讲清楚同时把我在实际项目里踩过的坑、验证过的参数、调试时用的排查思路一并分享出来。不管你是刚接触 emWin 的新手还是已经用过但总在中文显示上翻车的朋友下面这些内容都能直接拿去参考复现。整套流程我会尽量用“说人话”的方式展开涉及关键参数的地方会把计算过程和选择理由讲透涉及操作的地方会说明每一步的意图避免你照着做却不知道为什么这么做。1. 整体方案设计与选型思路拆解1.1 为什么中文字库在 emWin 里是个“特殊问题”emWin 本身是一个面向资源受限环境的图形库它的设计哲学是“按需加载、按需渲染”。英文字符只有 95 个可打印字符一套 ASCII 字库撑死几 KB随便一颗小 Flash 都放得下。但中文不一样常用汉字就有三千多个GB2312 一级字库 3755 个汉字二级字库再加 3008 个如果做全字库点阵数据量会迅速膨胀。以 16x16 点阵为例一个汉字占 32 字节3755 个汉字就是约 120KB加上二级字库接近 220KB。如果是 24x24 点阵单个汉字 72 字节全字库直接奔着 500KB 以上去了。这个体量对于很多只有 256KB 或 512KB 内部 Flash 的 MCU 来说压力非常现实。所以 emWin 处理中文的核心矛盾是字符集大、点阵数据占空间、RAM 和 Flash 都紧张。这就决定了不能像英文那样简单粗暴地全量加载必须做取舍。常见的取舍方式有三种只取需要的汉字子集、使用外部存储SPI Flash、SD 卡存放字库、使用矢量字体或者抗锯齿字体做压缩。这三种方式各有适用场景选错了后面会非常难受。我在实际项目里见过不少团队一开始图省事直接生成全字库塞进内部 Flash结果程序空间被吃掉一大半后面加功能时捉襟见肘只能回头重构。也见过为了省空间只放几十个汉字结果产品一出厂客户输入一个生僻字就显示方块售后成本反而更高。所以字库方案的选择本质上是在空间、灵活性、开发成本三者之间找平衡点这个判断要在项目早期就做掉。1.2 三种主流字库方案的对比与选择逻辑先把三种方案摆出来对比这样你心里有个谱方案存储位置空间占用灵活性适用场景内部 Flash 子集字库MCU 内部 Flash小几十 KB 到 200KB低汉字固定界面文字固定、汉字量可控的产品外部 Flash 全字库SPI Flash / NOR Flash大几百 KB 到数 MB高支持任意汉字需要用户输入、多语言、汉字量大的产品矢量/抗锯齿字体内部或外部均可中等取决于字号数量高支持缩放界面精致、需要多字号的产品选择逻辑其实很直接如果你的产品界面文字是固定的比如“设置”“运行”“故障”“温度”这类固定词条汉字总量不超过几百个那内部 Flash 子集字库是最省心的方案生成一次就不用管了。如果产品需要显示用户输入的内容、通讯录、日志信息汉字不可预知那就必须上外部 Flash 全字库或者至少是 GB2312 全字库。如果界面设计要求高需要 16、24、32 多种字号甚至要抗锯齿效果那矢量字体或者多字号点阵字库更合适但代价是生成和移植都更复杂。这里有个经验判断先统计产品所有界面文字加上可能的用户输入范围估算汉字总量。如果总量在 500 字以内内部 Flash 子集完全够用500 到 3000 字看 Flash 余量决定超过 3000 字或者需要动态输入直接考虑外部存储方案别犹豫。这个统计工作花半天时间能省后面几天的返工。1.3 emWin 字库机制的核心原理要理解 emWin 怎么显示中文得先搞清楚它的字体数据结构。emWin 的字体本质上是一个GUI_FONT结构体里面包含了字体属性高度、基线、间距等和字符信息表。对于点阵字体字符信息表里记录的是每个字符的点阵数据偏移和宽度。渲染时emWin 根据字符编码去查表找到对应的点阵数据然后逐行逐列画到显存或者 LCD 上。关键点在于编码方式。emWin 支持 ASCII、Unicode、GB2312 等多种编码中文字库通常用 GB2312 或者 Unicode。GB2312 编码下一个汉字用两个字节表示高字节和低字节都在 0xA1 到 0xFE 之间。emWin 查表时会根据这两个字节计算出字符在字库中的索引。Unicode 编码下汉字用两个字节表示BMP 范围内范围更广但字库生成时要做编码转换。emWin 提供的字库转换工具Font Converter可以把 Windows 字体或者点阵字库转成 C 文件生成GUI_FONT结构体和点阵数据数组。这个工具支持选择字符范围、编码方式、点阵大小、是否抗锯齿等参数。生成出来的 C 文件直接加入工程编译即可调用时用GUI_SetFont()设置字体然后用GUI_DispString()或者GUI_DispStringAt()显示字符串。这里有个容易忽略的细节emWin 默认的字符串显示函数对编码的处理方式。如果你用的是 GB2312 字库那传给GUI_DispString()的字符串也必须是 GB2312 编码否则查表会错位显示出来就是乱码。很多新手在 Keil 或者 IAR 里写中文字符串源文件编码是 UTF-8编译进去的字节序列和 GB2312 对不上屏幕上一片混乱。这个问题后面会专门讲排查方法。2. 字库生成与核心参数实操解析2.1 字库生成工具的获取与基本配置emWin 的字库转换工具通常随 emWin 源码包一起提供名字叫 FontCvt 或者 GUIFontConverter不同版本叫法略有差异。这个工具是 Windows 桌面程序界面不算精致但够用。打开后主要做几件事选择源字体、设定点阵大小、选择编码方式、指定字符范围、导出 C 文件。源字体选择上建议用等宽点阵字体或者经过优化的宋体/黑体。Windows 自带的宋体在小编号点阵下12x12、16x16效果一般笔画容易糊在一起尤其是“繁”“赢”这类笔画多的字。如果产品对显示效果有要求可以找专门的点阵字体比如一些开源的 16x16 点阵字库笔画清晰度明显更好。我一般 16x16 用宋体或者专用点阵字体24x24 以上用黑体或者微软雅黑效果比较稳。点阵大小选择要和 LCD 分辨率、界面布局匹配。常见的选择是 12x12小字用于状态栏、16x16正文最常用、24x24标题、32x32大标题。这里要注意点阵大小不等于字符高度emWin 字体结构里的高度包含了行间距实际字符点阵可能比设定值小一点。比如设定 16 点高度实际字符可能占 14 到 15 个像素剩下的是间距。这个细节在布局计算时要考虑进去否则文字可能贴边或者重叠。编码方式选择是重中之重。GB2312 编码兼容性好字库生成简单但字符范围有限生僻字不支持。Unicode 编码范围广但字库生成时要做映射而且字符串处理要注意字节序。我的建议是如果产品只面向国内、汉字范围可控优先 GB2312如果需要多语言或者生僻字用 Unicode。选错了后面改起来很麻烦因为字库文件和代码里的字符串编码都要跟着改。2.2 字符范围的选择策略与空间估算字符范围的选择直接决定字库大小。Font Converter 里可以按区块选择比如“ASCII”“GB2312 一级汉字”“GB2312 二级汉字”“标点符号”等。也可以手动输入需要的汉字或者导入一个文本文件工具会自动提取其中的字符。这里有个非常实用的技巧用产品界面文字生成自定义字符集。把产品所有界面上的文字、提示信息、菜单项整理成一个 txt 文件每行一个词条然后导入 Font Converter让它只生成这些字符的字库。这样生成的字体文件最小可能只有十几 KB而且完全够用。我做过一个工业面板项目界面文字统计下来 380 多个汉字16x16 点阵字库生成出来约 13KB塞进内部 Flash 毫无压力。空间估算的方法很简单汉字数量 × 每个汉字的字节数。16x16 点阵每个汉字 16×16÷8 32 字节24x24 点阵每个汉字 24×24÷8 72 字节32x32 点阵每个汉字 128 字节。再加上 ASCII 字符和字体结构体的开销一般几十到几百字节。比如 500 个汉字、16x16 点阵就是 500×32 16000 字节约 15.6KB加上 ASCII 和其他开销算 17KB 比较稳妥。如果要生成 GB2312 全字库一级汉字 3755 个16x16 点阵约 117KB24x24 约 264KB32x32 约 470KB。这个数据量心里要有数内部 Flash 不够就别硬塞。二级汉字再加 3008 个数据量再涨 80% 左右。所以全字库方案基本都要外部存储除非你的 MCU 有 1MB 以上 Flash 且不在乎占用。注意Font Converter 生成的字库文件里字符是按编码顺序排列的但不同版本工具生成的索引方式可能有差异。移植时一定要用同一版本工具生成的字库和对应的 emWin 库混用不同版本可能出现查表错位。2.3 生成 C 文件的结构解读Font Converter 导出的 C 文件通常包含几部分字体属性结构体、字符信息数组、点阵数据数组、以及一个GUI_FONT类型的字体对象。以 16x16 GB2312 字库为例结构大致如下不同版本字段名略有差异GUI_CONST_STORAGE GUI_FONT_PROP FontProp16[] { { 0xA1A1, 0xFEFE, FontCharInfo16[0], FontData16[0] }, { 0, 0, NULL, NULL } }; GUI_CONST_STORAGE GUI_CHARINFO FontCharInfo16[] { { 8, 16, 2, (void *)0x0000 }, { 8, 16, 2, (void *)0x0020 }, /* ... 更多字符信息 ... */ }; GUI_CONST_STORAGE unsigned char FontData16[] { /* 点阵数据每个汉字 32 字节 */ }; GUI_CONST_STORAGE GUI_FONT GUI_FontChinese16 { GUI_FONTTYPE_PROP_USER, 16, 16, 1, 1, FontProp16 };GUI_FONT_PROP结构体定义了字符范围起始编码、结束编码和对应的字符信息、点阵数据指针。GUI_CHARINFO里记录了每个字符的宽度、高度、每行字节数和点阵数据偏移。GUI_FONT结构体把这些串起来GUI_SetFont(GUI_FontChinese16)就能用了。理解这个结构的意义在于出问题时你能定位到是哪一层错了。比如显示方块可能是字符信息表里没有这个字符显示乱码可能是编码对不上显示花屏可能是点阵数据偏移算错了。后面排查章节会展开讲。3. 移植与代码调用的完整实操流程3.1 字库文件加入工程与编译配置生成的 C 文件加入工程很简单直接添加到源文件列表即可。但有几个编译配置要注意。第一字库文件通常很大要确认编译器不会对它做优化或者截断。有些编译器对超大数组有默认限制需要在链接脚本或者编译选项里调整。第二字库数据要放在只读存储区Flash用const修饰避免被加载到 RAM。emWin 的字体结构体本身已经用了GUI_CONST_STORAGE宏一般会映射到const但如果你自己改过宏定义要确认一下。第三注意字节对齐。有些平台对结构体对齐有要求字库文件里的数组如果对齐不对访问时可能触发硬件异常。emWin 的字体数据一般按字节访问问题不大但如果你的编译器默认按 4 字节对齐而字库数据是紧凑排列的可能出现读取错位。稳妥的做法是在字库文件开头加#pragma pack(1)或者对应的编译器指令确保结构体紧凑排列。编译后要检查生成的 map 文件确认字库数据确实放在了 Flash 区域大小符合预期。我见过一个案例字库文件被链接到了 RAM 区域结果程序一运行就内存不足查了半天才发现是链接脚本里没有把.rodata段正确映射。这个检查花不了几分钟但能避免很多莫名其妙的问题。3.2 字体初始化与显示调用的标准写法字库加入工程后调用流程很直接。先初始化 emWin然后设置字体再显示字符串。标准写法如下#include GUI.h #include DIALOG.h extern GUI_CONST_STORAGE GUI_FONT GUI_FontChinese16; void MainTask(void) { GUI_Init(); GUI_SetBkColor(GUI_WHITE); GUI_Clear(); GUI_SetColor(GUI_BLACK); GUI_SetFont(GUI_FontChinese16); GUI_DispStringAt(温度25℃, 10, 10); GUI_DispStringAt(运行状态正常, 10, 40); while (1) { GUI_Delay(100); } }这里的关键是GUI_SetFont()设置字体后后续的GUI_DispString()系列函数都会用这个字体。如果要混用中英文字体可以分别设置但要注意 emWin 的字体回退机制——如果当前字体里没有某个字符emWin 不会自动切换到其他字体而是显示空白或者默认字符。所以中文字库最好把 ASCII 字符也包含进去避免中英文混排时英文显示不出来。GUI_DispStringAt()的坐标是字符左上角的位置单位是像素。显示多行文字时Y 坐标要按字体高度递增。字体高度可以从GUI_FontChinese16.YSize或者GUI_GetFontSizeY()获取不要硬编码否则换字体时布局会乱。如果要显示动态内容比如温度值用GUI_DispStringAt()配合sprintf()格式化字符串即可。但要注意缓冲区大小和编码一致性。GB2312 字库下格式化字符串里的中文也必须是 GB2312 编码这个在源文件编码设置里要确认。3.3 源文件编码与字符串字节序的坑这是中文显示翻车的高发区必须单独拎出来讲。很多开发环境默认源文件编码是 UTF-8而 GB2312 字库要求字符串是 GB2312 编码。UTF-8 下“温”字占 3 个字节E6 B8 A9GB2312 下占 2 个字节CE C2。如果源文件是 UTF-8编译进去的字节序列和字库对不上emWin 查表时按两个字节一组解析直接错位显示出来就是乱码或者方块。解决办法有两个一是把源文件编码改成 GB2312在 Keil、IAR、Eclipse 里都可以设置单个文件的编码二是用 Unicode 字库源文件保持 UTF-8但字符串要转成 Unicode 宽字符调用GUI_DispString()时用对应的宽字符版本或者做转换。第一种方法简单直接适合纯中文产品第二种方法更规范适合多语言产品但代码里要做编码转换稍麻烦。我一般推荐第一种因为改动最小。具体操作在编辑器里把包含中文字符串的源文件另存为 GB2312 编码有些编辑器叫 ANSI 或者 GBK然后在工程设置里确认编译器按该编码处理。改完后重新编译用十六进制查看器确认字符串字节是 GB2312 序列再下载运行。这个步骤看起来笨但能省掉大量猜测时间。注意如果源文件里同时有中文注释和中文字符串改编码时注释也会跟着变。如果注释里有特殊符号可能变成乱码。建议改编码前先备份改完后检查一遍注释是否正常。3.4 外部 Flash 字库的加载方式如果字库放在外部 SPI Flash加载方式和内部 Flash 不同。emWin 支持从外部存储读取字体数据但需要提供一个读取回调函数或者把外部 Flash 映射到地址空间如果 MCU 支持 XIP。常见做法有两种一是把字库数据整个读到 RAM适合字库不大且 RAM 充足的情况二是用 emWin 的字体驱动接口按需从外部 Flash 读取适合大容量字库。按需读取的实现思路是定义一个GUI_FONT结构体字符信息表放在内部 Flash点阵数据放在外部 Flash渲染时通过回调函数读取点阵数据。emWin 提供了GUI_FONT_PROP和自定义读取函数的机制但配置起来比内部字库复杂。如果项目时间紧字库又不超过 200KB直接读到 RAM 更省事现在很多 MCU 有 128KB 以上 RAM挤一挤还是放得下的。外部 Flash 方案要注意读取速度。SPI Flash 的随机读取速度有限如果每显示一个字符都要读一次 Flash刷新率会受影响。优化方法是加缓存把常用字符的点阵数据缓存到 RAM或者一次性读取整行文字的点阵数据。这个优化在界面刷新频繁的场景下很有必要否则会出现明显的卡顿。4. 常见问题排查与避坑经验实录4.1 显示方块、乱码、花屏的定位思路中文显示出问题症状无非几种方块、乱码、花屏、部分字符缺失。每种症状对应的原因不同排查思路也不一样。我整理了一个速查表按症状定位原因症状可能原因排查方法全部显示方块字库未正确加载、字体指针为空检查GUI_SetFont()参数、确认字库对象已链接中文显示方块英文正常字库不含中文、编码不匹配检查字符范围、确认字符串编码显示乱码随机字符编码错位、字节序问题用十六进制查看字符串字节、核对字库编码显示花屏点阵错乱点阵数据偏移错误、对齐问题检查GUI_CHARINFO偏移、确认结构体对齐部分字符缺失字符范围未覆盖、字库子集不全核对字符集列表、补充缺失字符文字重叠或贴边字体高度计算错误、坐标不对用GUI_GetFontSizeY()获取实际高度排查时建议从简到繁先确认字体是否设置成功用GUI_GetFont()回读再确认字符串字节是否正确十六进制查看再确认字库里是否有对应字符查字符信息表最后才怀疑点阵数据。大部分问题在前两步就能定位不用一上来就怀疑底层。我遇到过一个典型案例界面英文正常中文全是方块。查了半天字库、编码都没问题最后发现是GUI_SetFont()调用在GUI_Init()之前字体设置被初始化覆盖了。这种顺序问题很隐蔽但排查时只要按流程走一遍就能发现。所以调用顺序也是排查项之一别忽略。4.2 字库空间超限的优化技巧字库太大塞不下是另一个高频问题。优化手段有几个层次。第一层是精简字符集只保留实际用到的汉字这个前面讲过效果最明显。第二层是降低点阵大小16x16 换成 12x12数据量减少 44%但可读性下降要看产品定位。第三层是压缩点阵数据emWin 支持一些压缩格式但配置复杂一般项目用不上。第四层是外置存储前面讲过适合大容量需求。还有一个容易被忽略的点ASCII 字符和中文分开处理。如果中文字库只包含汉字ASCII 用 emWin 自带的字体可以省掉中文字库里的 ASCII 部分。但这样中英文混排时要切换字体代码稍麻烦。如果中文字库已经包含了 ASCII那就没必要分开省不了多少空间还增加复杂度。实测下来最有效的优化还是精简字符集。一个产品界面文字通常不超过 500 个汉字加上提示信息和可能的用户输入范围1000 字以内基本能覆盖。1000 字、16x16 点阵约 32KB这个体量大多数 MCU 都能接受。如果连 32KB 都挤不出来那要考虑是不是选了不合适的 MCU或者界面设计过于复杂。4.3 刷新性能与显示效果的平衡中文显示的性能问题主要体现在刷新率上。点阵字体渲染时每个字符要逐行逐列画点字符越多、点阵越大耗时越长。16x16 汉字在 72MHz 的 MCU 上单个字符渲染大约几十微秒一屏 100 个字符就是几毫秒一般够用。但如果界面频繁刷新或者用 32x32 大字体耗时会明显增加。优化刷新性能的方法一是减少重绘区域用WM_InvalidateRect()只刷新变化的部分而不是整屏重绘二是用存储设备emWin 的GUI_MEMDEV可以把绘制内容先画到内存再一次性刷到 LCD减少闪烁三是避免频繁切换字体字体切换有开销能合并的绘制合并处理。显示效果方面16x16 点阵下笔画多的字容易糊这是点阵字体的固有局限。如果产品对显示效果要求高可以考虑 24x24 或者抗锯齿字体。抗锯齿字体边缘平滑但数据量更大渲染也更耗时要在效果和性能之间权衡。我的经验是工业产品 16x16 够用消费类产品 24x24 起步界面精致的产品上抗锯齿。4.4 多字号与字体切换的管理策略产品里往往需要多种字号比如标题 24x24、正文 16x16、状态栏 12x12。管理多套字库的策略是每套字库单独生成、单独定义字体对象用宏或者枚举管理字体指针。代码里根据场景切换字体切换后记得恢复避免影响后续绘制。#define FONT_TITLE (GUI_FontChinese24) #define FONT_BODY (GUI_FontChinese16) #define FONT_SMALL (GUI_FontChinese12) void DrawTitle(const char *text) { GUI_SetFont(FONT_TITLE); GUI_DispStringAt(text, 10, 10); GUI_SetFont(FONT_BODY); /* 恢复默认字体 */ }多字号字库的空间开销要提前算好。三套字库加起来可能超过 100KB内部 Flash 紧张的话要考虑共用字符集或者外置存储。另外不同字号的字符宽度不同布局计算要用对应的字体高度和宽度不能混用。注意字体切换后一定要恢复否则后续绘制会用错字体。建议封装一个带恢复的绘制函数或者用 emWin 的GUI_SetFont()返回值保存旧字体绘制完再设回去。5. 进阶技巧与项目落地建议5.1 用自定义字符集精准控制字库大小前面提过用界面文字生成自定义字符集这里展开讲具体操作。先把产品所有界面文字整理成文本包括菜单项、提示信息、单位符号、可能的用户输入范围。整理时要注意覆盖全漏掉一个字符产品上就显示方块。我的做法是从界面设计稿逐屏提取文字加上代码里所有字符串常量合并去重生成字符列表。然后把这个列表导入 Font Converter选择“从文件加载字符”或者类似选项工具会自动提取字符并生成字库。生成后要验证字符覆盖率写一个小脚本或者用工具检查字库是否包含列表里所有字符避免遗漏。这个验证步骤花几分钟能避免产品出厂后的尴尬。自定义字符集的另一个好处是可以包含特殊符号比如℃、Ω、±、箭头等。这些符号在标准字库里可能没有或者编码位置特殊自定义生成时可以一并包含避免显示时缺字符。5.2 字库版本管理与团队协作字库文件是二进制数据转成的 C 数组体积大、可读性差版本管理容易混乱。团队协作时建议字库源文件字体、字符列表、生成配置纳入版本管理生成的 C 文件也纳入但标注生成时间和工具版本。这样别人拿到工程能复现字库出问题时也能追溯。如果多人修改界面文字字符列表要集中管理避免各自生成字库导致不一致。我的做法是维护一个charset.txt所有人往里面加字符定期重新生成字库并提交。生成脚本也纳入版本管理一键重新生成减少手工操作出错。字库文件更新后要同步更新所有引用它的代码确认字体对象名称没变、字符范围没变。如果字符范围变了布局可能要调整这个要提前通知相关开发人员。5.3 从项目经验看几个容易忽略的细节最后分享几个实际项目里踩过的坑都是文档里不常写但很要命的细节。第一个是 LCD 驱动的影响。有些 LCD 驱动对坐标或者颜色格式有特殊要求emWin 渲染出来的点阵数据如果和驱动不匹配显示会偏色或者错位。排查时可以先显示纯色块和 ASCII 字符确认驱动正常再上中文。这个顺序能快速区分是驱动问题还是字库问题。第二个是内存对齐。前面提过结构体对齐这里补充一点如果字库数据放在外部 Flash 且通过 DMA 读取要注意 DMA 的对齐要求。有些 DMA 要求源地址 4 字节对齐字库数据紧凑排列可能不满足读取时出错。解决办法是在字库数据前加填充或者用 memcpy 中转。第三个是编译器优化。有些编译器对const大数组会做特殊处理比如放到特定段或者压缩存储。如果发现字库数据地址不对或者读取异常检查编译器的段分配和优化选项。我遇到过一次编译器把字库数据放到了未初始化的段运行时全是 0显示一片空白。查 map 文件才发现问题。第四个是字体高度和行距。emWin 字体结构里的YSize是字符高度加行距实际字符可能比YSize小。布局时如果用YSize做行距文字之间会有额外空隙如果用实际字符高度可能重叠。稳妥的做法是用GUI_GetFontSizeY()获取实际高度或者实测调整。这个细节在密集布局时特别明显值得花时间调一下。第五个是编码转换的边界情况。GB2312 编码里有些字符的第二个字节可能是 0x7F 或者特殊值处理字符串时要小心。比如按字节遍历字符串时遇到中文要跳两个字节不能按单字节处理否则会把汉字的第二个字节当成 ASCII 字符解析导致后续全部错位。写字符串处理函数时要判断字节范围区分 ASCII 和中文。这些细节单独看都不复杂但组合在一起就是中文显示翻车的各种姿势。我的建议是新项目上手时先用最小系统验证字库流程——一块开发板、一个简单界面、几个汉字跑通了再往产品上集成。这样出问题容易定位不会一上来就被复杂工程淹没。字库这件事说到底是个细致活。工具会用只是入门真正省时间的是对编码、对齐、空间这些底层细节的理解以及出问题时能快速定位的排查思路。上面这些内容如果能帮你少走几个弯路那这篇分享就值了。后续如果界面要支持多语言或者动态字体缩放字库方案还要再调整那是另一个话题有机会再展开聊。