01:2440----点灯大师

目录

一:点亮一个LED

1:原理图

2:寄存器

3:2440的框架和启动过程

A:框架

B:启动过程 

4:代码

5:ARM知识补充

6:c语言和汇编的应用

A:代码

B:分析汇编语言

C:内存空间

7:内部机制

二:点亮2个灯

三:流水灯

四:按键控制LED

1:原理图

2:寄存器配置

3:代码


一:点亮一个LED

1:原理图

        当LED输入低电平时出现电压差, LED被点亮  (n的意思是低电平有效)

        LED1 LED2 LED4分别接在 GPF4,5,6的IO口上

2:寄存器

        配置GPFCON寄存器的[9:8]位为0b01-----输出模式 ;  GPFCON--设置串口的模式

         GPFDAT寄存器: 当该端口配置为输入端口时,对应的位为引脚状态。当端口配置为输出端口时,引脚状态与对应的位相同。当端口配置为功能引脚时,将读取未定义值。

        GPF4对应GPFDAT寄存器的第4位, GPF[4]----0低电平/1高电平

        GPFDAT寄存器--设置串口具体输出的内容

3:2440的框架和启动过程

A:框架

注意: CPU----里面有许多寄存器(R0~R15) ; 在CPU里面的寄存器是可以直接访问的.

         GPIO控制器----里面有各种引脚,当然也包括我们今天使用的GPF4引脚;  GPIO控制器里面也有寄存器(GPFCON, GPCDAT),不过这里面的寄存器需要地址访问, 不能向CPU里面的寄存器直接访问.  在芯片手册中有寄存器的地址.

B:启动过程 

大多数的ARM芯片都是从0地址启动的, 当然这也包括我们讲述的2440

NOR启动 : NOR Flash基地址为0 ,  片内RAM的地址为0x4000 0000

        CPU读取出NOR第一个指令(前4个字节),执行

        CPU继续在读取出其他的指令在执行;   一边读取一边执行

Nand启动 : 片内4KARM基地址为0,  NOR启动不可访问

        2440硬件把Nand的前4K内容复制到片内RAM上,  然后CPU从0地址取出第条指令执行

4:代码

/*
*点亮一个LED
*/
.text
.global _start_start:
/* 配置GPFCON(0X56000050)寄存器的[9:8]位为01--输出模式*/ldr r1,=0X56000050ldr r0,=0x100str r0,[r1]
/*
*配置GPFDAT寄存器为低电平(0x56000054)--输出低电平
*/ldr r1,=0x56000054ldr r0,=0str r0,[r1]/*死循环*/
halt:b halt

        我们采用的是交叉编译的方法---使用window书写汇编代码-----将汇编代码传给虚拟机-----在虚拟机下将传来的汇编代码编译为bin文件-----在将bin文件传给window-------window烧写bin文件给Linux开发板;      我们使用的是GPIO控制器里面的寄存器所以必须使用地址进行访问

5:ARM知识补充

       程序计数器 R15: 寄存器 R15 保存程序计数器(PC),它总是用于特殊的用途。它经常可用于通用寄存器RO~R14 所使用的位置(即在指令编码中 R15 与 RO~R14 的地位一样,只是指令执行的结果不同),因此,可以认为它是一个通用寄存器。但是对于它的使用还有许多与指令相关的限制或特殊情况。这些将在具体的指令描述中介绍。通常,如果 R15 使用的方式超出了这些限制,那么指令将是不可预测的。

        当指令对 R15 的读取没有超过任何对 R15 使用的限制时,读取的值是指令的地址加上 8个字节。由于 ARM 指令总是以字为单位,结果的 Bit[1:0]总是为 0。这种读取 PC 的方式主要用于对附近的指令和数据进行快速、与位置无关的寻址,包括程序中与位置无关的转移。


        当使用 STR或 STM 指令保存 R15 时,出现了上述规则的一个例外。这些指令可将指令地址加 8字节保存(与其它指令读取 R15 一样)或将指令自身地址加 12 字节(将来还可能出现别的数据)。偏移量 8 还是 12(或是其它数值)取决于 ARM 的实现(也就是说,与芯片有关)。对于某个具体的芯片,它是个常量。这样使用 STR 和 STM 指令是不可移植的。


        由于这个例外,最好避免使用 STR 和 STM 指令来保存 R15。如果很难做到,那么应当在程序中使用合适的指令序列来确定当前使用的芯片所使用的偏移量

在2440中R15(pc)的偏移量为8, 注意取决于他对数据的读取方式

x的地址=x的地址+8

        当他在读取地址A指令的时候

        已经在对地址A+4的指令进行译码

        已经在读取地址A+8的指令

分析反汇编的代码:

led_on.elf:     file format elf32-littlearm
Disassembly of section .text:
00000000 <_start>:0:	e59f1014 	ldr	r1, [pc, #20]	; 1c <halt+0x4>4:	e3a00c01 	mov	r0, #256	; 0x1008:	e5810000 	str	r0, [r1]c:	e59f100c 	ldr	r1, [pc, #12]	; 20 <halt+0x8>10:	e3a00000 	mov	r0, #014:	e5810000 	str	r0, [r1]00000018 <halt>:18:	eafffffe 	b	18 <halt>1c:	56000050 	undefined instruction 0x5600005020:	56000054 	undefined instruction 0x56000054

由于我们使用的是伪指令;  他是不存在的指令,最会被拆分真正的几条ARM指令;

上面的汇编代码都是由伪指令拆分而来的

优点 : 他可以表示任意值;   

        ARM指令===>32位,但是如果使用MOV的话并不能表示32位, 因为MOV32位中的一些位是用来表示他自己的,剩下的不够32位,  剩下的不够32位也只能表示一些立即数

ldr r1,=0X56000050=====>伪指令

6:c语言和汇编的应用

A:代码

我们需要写一个汇编代码, 给main函数设置内存, 调用main函数

int main()
{unsigned int *pGPFCON = (unsigned int *)0x56000050;unsigned int *pGPFDAT = (unsigned int *)0x56000054;/* 配置GPF4为输出引脚 */*pGPFCON = 0x100;/* 设置GPF4输出0 */*pGPFDAT = 0;return 0;
}

.text
.global _start_start:/* 设置内存: sp 栈 */ldr sp, =4096  /* nand启动 */
//	ldr sp, =0x40000000+4096  /* nor启动 *//* 调用main */bl mainhalt:b halt
all:arm-linux-gcc -c -o led.o led.carm-linux-gcc -c -o start.o start.Sarm-linux-ld -Ttext 0 start.o led.o -o led.elfarm-linux-objcopy -O binary -S led.elf led.binarm-linux-objdump -D led.elf > led.dis
clean:rm *.bin *.o *.elf *.dis

我们使用makefile来编译, 避免重复多次的编译 

可以看到x.ids文件中的地址和给板子烧录的bin文件地址一致;

B:分析汇编语言

r0~r3寄存器负责----调用者和被调用者的传递参数的问题;

r4~r11寄存器在函数中,可能被使用, 所以在人口中保存他们, 在出口中恢复他们;


led.elf:     file format elf32-littlearmDisassembly of section .text:00000000 <_start>:0:	e3a0da01 	mov	sp, #4096	; 0x10004:	eb000000 	bl	c <main>00000008 <halt>:8:	eafffffe 	b	8 <halt>0000000c <main>:c:	e1a0c00d 	mov	ip, sp10:	e92dd800 	stmdb	sp!, {fp, ip, lr, pc}14:	e24cb004 	sub	fp, ip, #4	; 0x418:	e24dd008 	sub	sp, sp, #8	; 0x81c:	e3a03456 	mov	r3, #1442840576	; 0x5600000020:	e2833050 	add	r3, r3, #80	; 0x5024:	e50b3010 	str	r3, [fp, #-16]28:	e3a03456 	mov	r3, #1442840576	; 0x560000002c:	e2833054 	add	r3, r3, #84	; 0x5430:	e50b3014 	str	r3, [fp, #-20]34:	e51b2010 	ldr	r2, [fp, #-16]38:	e3a03c01 	mov	r3, #256	; 0x1003c:	e5823000 	str	r3, [r2]40:	e51b2014 	ldr	r2, [fp, #-20]44:	e3a03000 	mov	r3, #0	; 0x048:	e5823000 	str	r3, [r2]4c:	e3a03000 	mov	r3, #0	; 0x050:	e1a00003 	mov	r0, r354:	e24bd00c 	sub	sp, fp, #12	; 0xc58:	e89da800 	ldmia	sp, {fp, sp, pc}
Disassembly of section .comment:00000000 <.comment>:0:	43434700 	cmpmi	r3, #0	; 0x04:	4728203a 	undefined8:	2029554e 	eorcs	r5, r9, lr, asr #10c:	2e342e33 	mrccs	14, 1, r2, cr4, cr3, {1}10:	Address 0x10 is out of bounds.

C:内存空间

        内存空间被分为三个部分:代码段(text segment,即程序代码)、数据段(data segment,即变量)和栈段(stack segment)。数据段从下往上增长,而栈从上向下增长图。在这两者之间是空闲的地址空间。栈的增长是随着程序的执行自动进行的,而数据的扩展则需要通过brk 系统调用来显式地完成,brk有一个参数来指定数据段的结束地址,它可比当前值大(表示扩展数据段 ),或是比当前值小(表示缩小数据段 )。当然,这个参数必须小于指针,否则栈和数据段将会重叠,这是不允许的。

7:内部机制

二:点亮2个灯

        上面我们实现了被调用者给调用者传递参数;

        我们这里学习---调用者给被调用者传递参数

int len_on(int num)
{/*设置寄存器 点亮LED2*/unsigned int* GPFCON = 0x56000050;unsigned int* GPFDAT = 0x56000054;if (num == 4){/*设置输出模式*/*GPFCON = 0x100;}if (num == 5){/*设置输出模式*/*GPFCON = 0x400;}/*输出低电平*/*GPFDAT = 0;return 0;
}
void Delay(int n)
{while (n--);
}
/*
*点亮一个LED
*/
.text
.global _start_start:/*设置内存: sp栈*/ldr sp,=4096 /*nand启动*/ldr sp,=0x40000000+4096/*nor启动*/mov r0 ,#4bl len_onldr r0 ,=10000bl Delaymov r0 ,#5bl len_onhalt:b halt

led.elf:     file format elf32-littlearmDisassembly of section .text:00000000 <_start>:0:	e3a0da01 	mov	sp, #4096	; 0x10004:	e59fd018 	ldr	sp, [pc, #24]	; 24 <halt+0x4>8:	e3a00004 	mov	r0, #4c:	eb000006 	bl	2c <len_on>10:	e59f0010 	ldr	r0, [pc, #16]	; 28 <halt+0x8>14:	eb000022 	bl	a4 <Delay>18:	e3a00005 	mov	r0, #51c:	eb000002 	bl	2c <len_on>00000020 <halt>:20:	eafffffe 	b	20 <halt>24:	40001000 	andmi	r1, r0, r028:	00002710 	andeq	r2, r0, r0, lsl r70000002c <len_on>:2c:	e52db004 	push	{fp}		; (str fp, [sp, #-4]!)30:	e28db000 	add	fp, sp, #034:	e24dd014 	sub	sp, sp, #2038:	e50b0010 	str	r0, [fp, #-16]3c:	e59f3058 	ldr	r3, [pc, #88]	; 9c <len_on+0x70>40:	e50b300c 	str	r3, [fp, #-12]44:	e59f3054 	ldr	r3, [pc, #84]	; a0 <len_on+0x74>48:	e50b3008 	str	r3, [fp, #-8]4c:	e51b3010 	ldr	r3, [fp, #-16]50:	e3530004 	cmp	r3, #454:	1a000002 	bne	64 <len_on+0x38>58:	e51b300c 	ldr	r3, [fp, #-12]5c:	e3a02c01 	mov	r2, #256	; 0x10060:	e5832000 	str	r2, [r3]64:	e51b3010 	ldr	r3, [fp, #-16]68:	e3530005 	cmp	r3, #56c:	1a000002 	bne	7c <len_on+0x50>70:	e51b300c 	ldr	r3, [fp, #-12]74:	e3a02b01 	mov	r2, #1024	; 0x40078:	e5832000 	str	r2, [r3]7c:	e51b3008 	ldr	r3, [fp, #-8]80:	e3a02000 	mov	r2, #084:	e5832000 	str	r2, [r3]88:	e3a03000 	mov	r3, #08c:	e1a00003 	mov	r0, r390:	e28bd000 	add	sp, fp, #094:	e8bd0800 	pop	{fp}98:	e12fff1e 	bx	lr9c:	56000050 	undefined instruction 0x56000050a0:	56000054 	undefined instruction 0x56000054000000a4 <Delay>:a4:	e52db004 	push	{fp}		; (str fp, [sp, #-4]!)a8:	e28db000 	add	fp, sp, #0ac:	e24dd00c 	sub	sp, sp, #12b0:	e50b0008 	str	r0, [fp, #-8]b4:	e51b3008 	ldr	r3, [fp, #-8]b8:	e3530000 	cmp	r3, #0bc:	03a03000 	moveq	r3, #0c0:	13a03001 	movne	r3, #1c4:	e20330ff 	and	r3, r3, #255	; 0xffc8:	e51b2008 	ldr	r2, [fp, #-8]cc:	e2422001 	sub	r2, r2, #1d0:	e50b2008 	str	r2, [fp, #-8]d4:	e3530000 	cmp	r3, #0d8:	1afffff5 	bne	b4 <Delay+0x10>dc:	e28bd000 	add	sp, fp, #0e0:	e8bd0800 	pop	{fp}e4:	e12fff1e 	bx	lrDisassembly of section .ARM.attributes:00000000 <.ARM.attributes>:0:	00002541 	andeq	r2, r0, r1, asr #104:	61656100 	cmnvs	r5, r0, lsl #28:	01006962 	tsteq	r0, r2, ror #18c:	0000001b 	andeq	r0, r0, fp, lsl r010:	00543405 	subseq	r3, r4, r5, lsl #814:	01080206 	tsteq	r8, r6, lsl #418:	04120109 	ldreq	r0, [r2], #-265	; 0x1091c:	01150114 	tsteq	r5, r4, lsl r120:	01180317 	tsteq	r8, r7, lsl r324:	Address 0x00000024 is out of bounds.Disassembly of section .comment:00000000 <.comment>:0:	3a434347 	bcc	10d0d24 <__bss_end__+0x10c8c3c>4:	74632820 	strbtvc	r2, [r3], #-2080	; 0x8208:	312d676e 	teqcc	sp, lr, ror #14c:	312e362e 	teqcc	lr, lr, lsr #1210:	2e342029 	cdpcs	0, 3, cr2, cr4, cr9, {1}14:	00332e34 	eorseq	r2, r3, r4, lsr lr

        对于2440他的内部同样存在看门狗, 我们在程序中没有对看门狗进行操作; 所以他在一段时间就会复位. 

三:流水灯


void Delay(int n)
{while (n--);
}int main(void)
{int i = 4;/*设置寄存器 点亮LED2*/volatile unsigned int* GPFCON = (volatile unsigned int*)0x56000050;volatile unsigned int* GPFDAT = (volatile unsigned int*)0x56000054;/*设置GPF4/5/6位位输出模式*/*GPFCON &= ~((3 << 8) | (3 << 10) | (3 << 12)); //3对应0b11 清位*GPFCON |= ((1 << 8) | (1 << 10) | (1 << 12)); //置1-设置位输出模式/*GPFDAT寄存器配置 */*GPFDAT &= ~((1 << 4) | (1 << 5) | (1 << 6)); //清位*GPFDAT |= ((1 << 4) | (1 << 5) | (1 << 6)); //把GPFDAT寄存器的4 5 6 位置1--灭灯/*led4 0x100 led5 0x400 */while (1){	if (i == 7)i = 4;*GPFDAT &= ~(1 << i);Delay(10000);*GPFDAT |= (1 << i);Delay(10000);i++;		}return 0;
}

/*
*点亮一个LED
*/
.text
.global _start_start:
/* 关闭看门狗 */ldr r0, =0x53000000ldr r1, =0str r1, [r0]/*设置内存: sp栈* 我们判断是nor启动还是nand启动/mov r1, #0ldr r0, [r1] /* 读出原来的值备份 */str r1, [r1] /* 0->[0] */ ldr r2, [r1] /* r2=[0] */cmp r1, r2   /* r1==r2? 如果相等表示是NAND启动 */ldr sp, =0x40000000+4096 /* 先假设是nor启动 */moveq sp, #4096  /* nand启动 */streq r0, [r1]   /* 恢复原来的值 */bl mainhalt:b halt

1:看门狗问题的解决

        看门狗定时器控制(WTCON)寄存器WTCON寄存器允许用户启用/禁用看门狗定时器,选择来自4个不同源的时钟信号,启用/禁用中断,启用/禁用看门狗定时器输出。看门狗定时器用于S3C2440A上电后功能异常重启时恢复;如果不希望控制器重启,则关闭看门狗定时器

我们可以看到当 Reset enable/disable (重新启用/禁用) 设置WTCON寄存器位0时, 2400就会关闭我们的寄存器

2:如何区分是nar启动还是nand启动

        nor启动 : 可以向内存一样读, 但是不能向内存一样写; (如果一定要写的话需要发送一定格式的数据才可以写)

        nand : 可读取写

方法: 分辨是nor/nand启动
     * 写0到0地址, 再读出来
     * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动 (nand-- 可读取写)
     * 否则就是nor启动  (只能读)

四:按键控制LED

1:原理图

平时他为高电平,  当按键按下他为低电平;  

2:寄存器配置

GPG3

GPF

按键位输入模式

3:代码

void Delay(int n)
{while (n--);
}#define GPFCON (*((volatile unsigned int*)0x56000050))
#define GPFDAT (*((volatile unsigned int*)0x56000054))
#define GPGCON  (*((volatile unsigned int*)0x56000060))
#define GPGDAT (*((volatile unsigned int*)0x56000064))
int main(void)
{/*volatile unsigned int* GPFCON = (volatile unsigned int*)0x56000050;volatile unsigned int* GPFDAT = (volatile unsigned int*)0x56000054;volatile unsigned int* GPGCON = (volatile unsigned int*)0x56000060;volatile unsigned int* GPGDAT = (volatile unsigned int*)0x56000064;*//*设置GPF4/5/6位p 位输出模式*/GPFCON &= ~((3 << 8) | (3 << 10) | (3 << 12)); //3对应0b11 清位GPFCON |= ((1 << 8) | (1 << 10) | (1 << 12)); //置1-设置位输出模式/*设置GPF0和GPF2按键位输入模式*/GPFCON &= ~((3 << 0) | (3 << 4));/*GPG3位输入模式*/GPGCON &= ~(3 << 6);while (1){	if (GPFDAT & (1 << 0)) /* s2 --> gpf6 */{/* 松开 */GPFDAT |= (1 << 6);}else{/* 按下 */GPFDAT &= ~(1 << 6);}if (GPFDAT & (1 << 2)) /* s3 --> gpf5 */{/* 松开 */GPFDAT |= (1 << 5);}else{/* 按下 */GPFDAT &= ~(1 << 5);}if ((GPGDAT & (1 << 3))==0) /* s4 --> gpf4 */{/* 按下 */GPFDAT &= ~(1 << 4);}else{/* 松开 */GPFDAT |= (1 << 4);	}}return 0;
}
/*
*点亮一个LED
*/
.text
.global _start_start:
/* 配置GPFCON(0X56000050)寄存器的[9:8]位为01--输出模式*/ldr r1,=0X56000050ldr r0,=0x100str r0,[r1]
/*
*配置GPFDAT寄存器为低电平(0x56000054)--输出低电平
*/ldr r1,=0x56000054ldr r0,=0str r0,[r1]/*死循环*/
halt:b halt

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

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

相关文章

python实现炒股自动化,个人账户无门槛量化交易的开始

本篇作为系列教程的引子&#xff0c;对股票量化程序化自动交易感兴趣的朋友可以关注我&#xff0c;现在只是个粗略计划&#xff0c;后续会根据需要重新调整&#xff0c;并陆续添加内容。 股票量化程序化自动交易接口 很多人在找股票个人账户实现程序化自动交易的接口&#xff0…

Django——orm模块创建表关系

django orm中如何创建表关系 1. 表关系分析 表与表之间的关系: 一对多 多对多 一对一 没有关系 判断表关系的方法: 换位思考用4张表举例: 图书表 出版社表 作者表 作者详情表图书和出版社是一对多的关系 外键字段建在多的那一方图书和作者是多对多的关系 需要创建第三张表来…

(离散数学)命题及命题的真值

答案&#xff1a; &#xff08;5&#xff09;不是命题&#xff0c;因为真值不止一个 &#xff08;6&#xff09;不是命题&#xff0c;因为不是陈述句 &#xff08;7&#xff09;不是命题&#xff0c;因为不是陈述句 &#xff08;8&#xff09;不是命题&#xff0c;真值不唯一

Qt 自定义按钮 区分点按与长按信号,适配触摸事件

Qt 自定义按钮 区分点按与长按信号 适配触摸事件 效果 使用示例 // 点按connect(ui.btnLeft, &JogButton::stepclicked, this, &MainWindow::btnLeft_clicked);// 长按开始connect(ui.btnLeft, &JogButton::continueOn, this, &MainWindow::slotJogLeftOn);//…

Linux 基于 LVM 逻辑卷的磁盘管理【简明教程】

一、传统磁盘管理的弊端 传统的磁盘管理&#xff1a;使用MBR先对硬盘分区&#xff0c;然后对分区进行文件系统的格式化最后再将该分区挂载上去。 传统的磁盘管理当分区没有空间使用进行扩展时&#xff0c;操作比较麻烦。分区使用空间已经满了&#xff0c;不再够用了&#xff…

Qt 事件循环

引出 UI程序之所叫UI程序&#xff0c;是因为需要与用户有交互&#xff0c;用户交互一般是通过鼠标键盘等的输入设备&#xff0c;那UI程序就需要有能随时响应用户交互的能力。 一个C程序的main函数大概是下面这样&#xff1a; int main() {...return 0; } 我们如何使程序能随…

深度学习之基于Pytorch框架的MNIST手写数字识别

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 MNIST是一个手写数字识别的数据集&#xff0c;是深度学习中最常用的数据集之一。基于Pytorch框架的MNIST手写数字识…

Linux - 基础IO(重定向 - 重定向模拟实现 - shell 当中的 重定向)- 下篇

前言 上一篇博客当中&#xff0c;我们对 文件 在操作系统当中是 如何就管理的&#xff0c;这个问题做了 详细描述&#xff0c;本篇博客将基于上篇 博客当中的内容进行 阐述&#xff0c;如有疑问&#xff0c;请参考上篇博客&#xff1a; Linux - 基础IO&#xff08;Linux 当中…

Git之分支与版本

&#x1f3ac; 艳艳耶✌️&#xff1a;个人主页 &#x1f525; 个人专栏 &#xff1a;《Spring与Mybatis集成整合》《Vue.js使用》 ⛺️ 越努力 &#xff0c;越幸运。 1.开发测试上线git的使用 1.1. 环境讲述 当软件从开发到正式环境部署的过程中&#xff0c;不同环境的作用…

adb and 软件架构笔记

Native Service&#xff0c;这是Android系统里的一种特色&#xff0c;就是通过C或是C代码写出来的&#xff0c;供Java进行远程调用的Remote Service&#xff0c;因为C/C代码生成的是Native代码&#xff08;机器代码&#xff09;&#xff0c;于是叫Native Service。 native服务…

可视化 | echarts中国地图散点图

改编自echarts添加地图散点 &#x1f4da;改编点 roam: false&#xff1a;不允许放缩拖动 地图颜色修改 geo: {show: true,top: 15%,map: name,label: {normal: {show: false},emphasis: {show: true,color: "#fff",}},roam: false,itemStyle: {normal: {areaColor…

深度学习 opencv python 实现中国交通标志识别 计算机竞赛_1

文章目录 0 前言1 yolov5实现中国交通标志检测2.算法原理2.1 算法简介2.2网络架构2.3 关键代码 3 数据集处理3.1 VOC格式介绍3.2 将中国交通标志检测数据集CCTSDB数据转换成VOC数据格式3.3 手动标注数据集 4 模型训练5 实现效果5.1 视频效果 6 最后 0 前言 &#x1f525; 优质…