《匠人手记》推荐网上购书渠道:
互动出版网(china-pub)购书入口   >>>
当当网(dangdang)购书入口   >>>
卓越亚马逊网 购书入口   >>>
淘宝网(taobao)购书入口   >>>
更多购书渠道……   >>> 

设为首页加入收藏联系匠人管理入口21IC首页21IC博客21IC社区侃单片机回复的贴参与的贴

天气预报
百宝日历
载入中...

百宝专栏

载入中...
最新货色

载入中...

粉丝评论

载入中...

载入中...



百宝信息

载入中...

百宝流量

(2006-07-01开始)


匠人手记

 匠人观点: 好记性不如烂笔头  
 黑色幽默:三鹿门——后世畅想

ELAN汇编小程序
pcbomb 发表于 2008-1-16 13:28:00  阅读全文 | 回复(0) | 引用通告 | 编辑

BCD转换成Binary
由于EM78XXX是8位的微控器,因此为了节省内存,我们的范例仅以一个BYTE存放两位BCD数为例,数字的范围在0~99之间,转换后的结果放在ACC,如果您需要更多的位数,相信您在看完之后应该不难自行修改才是。
程序一
这个范例程序共花费13个指令CYCLE,需要两个变量空间,执行后会影响到原BCD的内容。
MOV A,BCD
MOV TMP,A
MOV A,@0x0F
AND TMP,A
SWAP BCD
AND BCD,A
BC PSW,0
RLC BCD ; *2
MOV A,BCD
ADD TMP,A
RLC BCD
RLCA BCD ; *8
ADD A,TMP
说明
在程序一中所采用的方式应该算是最多人知道的方式,也是一种最直觉的方法,先将BCD个位数保存起来,因为十位数必须要乘以10,所以利用移位的技巧乘以10再加上个位数,所得的答案放入ACC。
程序二
在程序一的缺点,就是在执行程序以后,原本BCD的内容已经在移位的过程中被破坏掉了,为了改善这项缺失,我们换一种方式看看。下面这个程序,我们企图改善前面的缺失,共花费11个指令CYCLE,仍需要两个变量空间,但是执行后不会破坏原来BCD的内容。
SWAPA BCD
MOV TMP,A
MOV A,@0x0F
AND BCD,A
AND TMP,A
BC PSW,0
RLCA TMP
SWAP TMP
RRC TMP
ADD A,TMP
ADD A,BCD
程序三
对于程序二的结果我们仍然不满意,似乎稍嫌复杂,虽然速度有所改善,但在内存的分配上仍有余地,所以我们再改善成程序三的型态。转换过程只花费10个指令CYCLE,而且只需要一个变量空间,执行之后也不会改变原来BCD的内容。
MOV A,@0x0f
AND A,BCD
JBC BCD,4
ADD A,@10
JBC BCD,5
ADD A,@20
JBC BCD,6
ADD A,@40
JBC BCD,7
ADD A,@80
说明
看过以上三个范例,您是否觉得程序三最简洁而且容易了解?写程序的确是一项极具挑战性的工作,而且还可以找到很多灵感及乐趣,想不到吧!
Binary转换成BCD码
下面的范例程序会将存放在ACC内的二进制数转换成两位BCD码(Compacted BCD Code),可转换最大的BCD码是99。
CLR BCD
DIGIT_HI:
ADD A,@256-10
JBS PSW,FC
JMP DIGIT_LO
INC BCD
JMP DIGIT_HI
DIGIT_LO:
ADD A,@10
SWAP BCD
OR BCD,A
减法的陷阱
EM78系列汇编语言的减法指令是SUB,使用这个指令时您得特别注意,因为ACC永远都是减数,不可为被减数。SUB指令的语法有以下三种:
SUB A,R (R-A→A)
SUB R,A (R-A→R)
SUB A,K (K-A→A)
也就是说如果我们想计算A-2的值,如果写成:
SUB A,@2
其实是执行2-A,解决方法如下:
ADD A,@256-2 或
ADD A,@254
交换两组缓存器的内容
如果你觉得要交换两组内存的内容一定要借用第三组变量,那么您可以参考以下的方式,只是用了一些数学技巧就变得又快又简单。
MOV A,REG1
SUB A,REG2
ADD REG1,A
SUB REG2,A
原理说明
A=REG1
A=REG2-REG1
REG1=REG1+A
=REG1+(REG2-REG1)
=REG2
REG2=REG2-(REG2-REG1)
=REG1
若X>Y就交换...
延续上一个例子,此法用应用在Bubble Sort特别管用。
MOV A,X
SUB A,Y
JBC PSW,FC
JMP NO_CHANGE
ADD X,A
SUB Y,A
2补码
2补码加法经常代替减法,传统上的做法是先取1补码,然后加1。
COM REG
INC REG
或是可以利用另一种方式求得,所不同的是第二种方式会影响PSW缓存器。
ADD A,REG
SUB A,REG
如果您所要求的数已经放在ACC里面,那只要一行就能解决了。
SUB A,@0
旋转字节运算
在8051指令中位左旋有RLC与RL两种指令区分,RLC在ACC左旋时会连带将CY一并旋转,而RL只会将ACC的MSB旋入LSB。EM78XXX指令只有RLC,那么要如何才能做到不带CY旋转呢?答案是旋转两次:
RLCA REG1
RLC REG1
如图1所示,第一次位旋转并没有真正改变REG1的内容,目的是将REG1的MSB先放入FC,第二次位旋转才将刚刚放在FC内的MSB旋入LSB。同理,两个BYTES不经FC的位旋转也是相同的原理。
RLCA HI_BYTE
RLC LO_BYTE
RLC HI_BYTE
范围判断
写程序免不了会碰到IF..THEN..的场合,有些人觉得EM78XXX的条件判断式太过繁琐,所以笔者也将它们整理归纳一下。条件判断式可分为开放区间条件式与封闭区间条件式来讨论,以图2来表示。
开放条件式是以N点为出发点,当待测值大于N或是小于等于N时的条件判断,以C的语法描述如下:
if(number>n)
... /* number大于N */
else
... /* number小于等于N */
EM78XXX汇编语言写法如下:
MOV A,@N+1
SUB A,Number
JBC PSW,FC
JMP LABEL_1 ; 大于N
JMP LABEL_2 ; 小于等于N
封闭式条件判断是指待测值N是否在X与Y的范围之内,若以C的语法描述:
if((number>=x) && (number<=y))
.... /* in range */
else
.... /* False */
如何以EM78汇编语言做到呢?一般做法是以减法后的PSW做条件判断,程序如下:
MOV A,@2
SUB A,number
JBS PSW,FC
JMP FALSE
MOV A,@y+1
SUB A,SI
JBC PSW,FC
JMP FALSE
IN_RANGE:
; ....
FALSE:
; ....
这个IF条件式要花费8个指令Cycle,还不算太复杂。但是还有个更简洁的方法,以下用加法后的PSW(R3)做条件判断,一共只要5行就清洁溜溜了。
MOV A,Number
ADD A,@255-y
ADD A,@y-x+1
JBC PSW,FC
JMP IN_RANGE
FALSE:
; ....
IN_RANGE:
; ....
说明
关键就在前三行,x表示条件式的下限值,y表示条件式的上限值,可以看得出仍是利用CY旗标制造的特效,不但精简而且有点小聪明,许多老手都爱用,这也是他们口袋里的秘密武器之一。如果您觉得不错,不妨也收入锦囊中,尔后就可以依样画葫芦了。
ACC与缓存器内容交换
这理我们要介绍一种快速的逻辑算法,只需要3个指令CYCLE,就可以将ACC的内容与缓存器的内容交换,不拖泥带水,Very cute!
XOR Number,A
XOR A,Number
XOR Number,A
请读者自行在纸上推算一次,就知道答案了。
交换多组缓存器内容
利用上面介绍的方法,可以推广到多组缓存器交换的例子上,下面的程序将5组DATA内容移位,第一笔缓存器的数据传到第二笔缓存器内,第二缓存器的数据再传送到第三笔缓存器内,依此类推,最后一笔数据则传给第一个缓存器,形成一种字节数据旋转。
MOV A,@5
MOV COUNT,A
MOV A,@DATA1
MOV RSR,A
MOV A,DATA5
NEXT:
XOR INDIR,A
XOR A,INDIR
XOR INDIR,A
INC RSR
DJZ COUNT
JMP NEXT
计算MOD 2N
假如你刚好需要计算ACC MOD X,且X刚好是2的N次方,使用ACC AND
(X-1)是最快的方法了。例如要判断YEAR是否为闰年,有个简单的方法,可以排除一些非闰年的条件,只要不能被4整除者就不是闰年。所以可以用YEAR
AND 3解决。
MOV A,@4-1
AND A,YEAR
JBS PSW,FZ
JMP NOLEAP
清除一段连续的内存
对于连续一段内存做读写最好的方式就是使用间接寻址法,但是要注意在一些如M78447/811/860等高阶MCU,内存20H~
3FH又可以分成4组BANK,如果之前没有切换到正确的BANK会造成读写错误。下面的范例程序会将BANK1内的32个BYTES全部清为0。
INDIR == 0x00
RSR == 0x04
COUNT == 0x10
REG == 0x20
BANK1 == 0x40
BANK2 == 0x80
BANK3 == 0xC0
MOV A,@32
MOV COUNT,A
MOV A,@REG|BANK1
MOV RSR,A
NEXT:
CLR INDIR
INC RSR
DJZ COUNT
JMP NEXT
计算一个BYTE中有多少个"1"
这个小程序可以检查出在某个BYTE中共有几个1,在某些算法的过程可能会用得到,计算的结果放在ACC。
RRCA DATA
AND A,@0x55
SUB DATA,A
MOV A,DATA
AND A,@0x33
ADD DATA,A
RRC DATA
ADD DATA,A
RRC DATA
SWAPA DATA
ADD A,DATA
AND A,@0x0F
节省NOP指令的方法
您还在为程序挤不下伤脑筋吗?NOP指令有时候在延迟指令时间很有用,假如你有连续两个NOP指令可以用JMP到下一个指令的方式代替,因为这样可以减少一个指令BYTE,又可以达到相同的效果。
例如:
NOP
NOP
可以写成:
JMP NEXT_INST
NEXT_INST:
;....
因为一个NOP花费一个指令Cycle,但是一个JMP指令就需要2个指令Cycle,虽然有时候会抱怨JMP指令会多花一点时间,但是想不到它也有如此妙用吧。
LABEL太多?
写汇编语言最令人伤脑筋的问题之一就是程序中到处是label,这有两个坏处,第一就是不小心就会造成label重复的问题,第二就是想不出适当的label名称。如果您已经为label的命名问题肠枯思竭,给您提供一个小方法,程序中如果用「$」可以表示目前PC的地址,依此推论「$+2」表示PC+2,「$-4」表示PC-4,看看底下的例子您立刻就明白:
MOV R,R
JBS PSW,FZ
JMP $+2
JMP $-4
; ....
不过也要给您一个建议,label有个重要的意义就是具有批注的功能,特别是针对一些懒的写批注的人格外重要。所以这个方法仅适合使用在重复性很高的程序片断。
SWITCH...CASE叙述
在程序设计的过程中,免不了常常会碰到多重选项的问题,利用EM78XXX的查表指令试试看,所以TBL除了当作一般查表指令以外,还可以当作多重条件判断之用。
MOV A,CASE
TBL
JMP EVENT1 ; CASE="0"
JMP EVENT2 ; CASE="1"
JMP EVENT3 ; CASE="2"
JMP EVENT4 ; CASE="3"
多字节的递增及递减运算
因为EM78XXX是8位的单芯片,如果要执行8位以上的计算,必须将多个字节看成是一个变量,以下我们举例说明如何将一组24位的变量,做到递增及递减运算。
递增(Increment)
MOV A,@1
ADD INT24,A
JBC STATUS,FC
ADD INT24+1,A
JBC STATUS,FC
ADD INT24+2,A
递减(Decrement)
MOV A,@1
SUB INT24,A
JBS STATUS,FC
SUB INT24+1,A
JBS STATUS,FC
SUB INT24+2,A
判断多字节变量是否为零
使用简单的逻辑运算指令,将多字节OR在一起,然后依据Z旗标就可以判断此多字节变量是否为零了。
MOV A,INT24
OR A,INT24+1
OR A,INT24+2
JBS PSW,FZ
;...
复制某些位
有时候我们需要将一些特定的几个位由某个缓存器复制给另一组缓存器,由于并非完全复制缓存器的内容,所以会多了一些抽取位的步骤,现在我们找到一个方法,只要四个步骤就可以将指定的位复制到另一组缓存器里面,举例说明,假设位复制前(SOURCE)=44H,(TARGET)=5AH,如果我们希望将SOURCE的BIT0~BIT2复制到TARGET,则执行程序后(SOURCE)=44H,(TARGET)=5CH。
MOV A,SOURCE
XOR A,TARGET
AND A,@00000111B
XOR TARGET,A
无论您希望复制哪几个BIT,只要将第三行程序MASK所需的位即可。
奇偶位对调
以下这段程序是根据Dmitry
Kiryashov的算法设计,假设原本ACC内所有位的排列顺序为abcdefgh,交换后ACC顺序变成badcfehg,程序只有五行,颇耐人寻味。
MOV REG,A
AND A,@0x55
ADD REG,A
RRC REG
ADD A,REG
中断程序不需保留ACC及PSW的方法
中断程序一定要保留ACC及PSW吗?那倒未必!特别是如果您使用的是EM78P152/156之类的迷你级的MCU,RAM
SIZE都特别小,如果您只需要让TCC中断做简单的计数工作,只要小心使用指令,就可以避免中断程序会破坏到ACC及PSW。原因是有些指令并不会对PSW产生影响,有些指令不需要经过ACC。首先设定好预除器,并且让TCC
Free Run。下面的例子完全没用到ACC及PSW。
ORG 0
JMP INIT
ORG 8
TCCINT:
BC RF,TCIF ;清除中断旗标
INC COUNTER
RETI
Multiple Task管理与状态机
Multiple
Task就是将CPU时间平均分配(也可以是不平均分配)给多个Task,所以在程序中会有一个时间管理者,依照指定的时间对指定的Task服务,没有分配到时间的Task必需等候时间到来才能执行。
TCCINT:
MOV R10,A
SWAP R10
SWAPA PSW
MOV R11,A
INC TASK
MOV A,@4
SUB A,TASK
JBS PSW,FC
JMP ENDINT
CLR TASK
ENDINT:
BC ISR,TCIF
SWAPA R11
MOV PSW,A
SWAPA R10
RETI
;-------------------------
MAIN:
MOV A,@0x21
CONTW
CLR TCC
CLR ISR
MOV A,@0x01
IOW IOCF
CLR TASK
START:
MOV A,TASK
TBL
JMP TASK0
JMP TASK1
JMP TASK2
JMP TASK3
JMP TASK4
;-------------------------
TASK0:
; ....
JMP START
TASK1:
; ....
JMP START
TASK2:
; ....
JMP START
TASK3:
; ....
JMP START
TASK4:
; ....
JMP START
上面这个程序将TCC规划为62.5ms中断一次(系统震荡选用32.768KHz),所以Task每62.5ms会切换到下一个Task,也就是说每个Task都能够平均分享CPU的时间,这就是分时多任务的原理。至于中断程序部分不是必须的,可一情况决定是否要由TCC安排时间的管理。状态机(State
Machine)是根据目前所在的State所产生的条件,来决定下一个状态,所以程序原理和上面这个例子大同小异,所不同的是,我们应该把标示为TASKn的Label视为一个单独的State,然后根据某些条件将最后面的JMP转移到另外一个State。在这里时间控制也不一定要用到,视需求决定。例如:
TASK1:
MOV A,INPUT
JBS PSW,FZ
JMP TASK2
JMP TASK3
说明
如果INPUT=0的话,将由目前所在的TASK1转移到TASK3执行,否则状态转移到TASK2。
后记
戏法人人会变,只是巧妙各有不同,希望笔者提供的这些小技巧对于喜欢玩单芯片的读者能够有所帮助,我们不仅只是强调硬件应该节省,在软件技巧上也应该多发展一些好的算法,如此才能双管齐下,对症下药。吾人期盼藉此抛砖引玉能激发您更多的创意,写出更精简的程序,也期盼您的指教。
竭诚欢迎所有喜爱EM78x系列单芯片的朋友来信和我们一起讨论。
笔者E-MAIL:sa2tjw@emc.com.tw

pheavecn 赏析1:

引:
ACC与缓存器内容交换
这理我们要介绍一种快速的逻辑算法,只需要3个指令CYCLE,就可以将ACC的内容与缓存器的内容交换,不拖泥带水,Very cute!
XOR Number,A
XOR A,Number
XOR Number,A
======================================
析:
这个技巧是所有单片机都通用的。也是我们最多机会用到的。
大多数场合的做法是:
MOV Temp1,A
MOV A,Number
MOV Temp2,A
MOV A,Temp1
MOV Number,A
MOV a,Temp2
用了2个过程变量,6个指令周期。
XOR指令实际上是按位做二进制加法,进位丢弃。
称Number的0位(bit0)为Bn,A的0位为Ba;
算法的分析如下:
1、XOR Number,A ;Bn+Ba==>Number的0位
2、XOR A,Number
;Bn+Ba+Ba==>ACC的0位,由于Ba+Ba必等于0,所以现在ACC的0位就等于Bn,即原来Number的0位。
3、XOR Number,A
;Bn+Ba+Bn==>Number的0位,同上理,Bn+Bn必等于0,现在Number的0位等于原来ACC的0位。
必须注意,XOR指令影响Z标志位。
HOLTEK芯片的实现指令为:
XORM A,Number
XOR A,Number
XORM A,Number
同样只影响Z标志位。

pheavecn 赏析2:

引:
节省NOP指令的方法
您还在为程序挤不下伤脑筋吗?NOP指令有时候在延迟指令时间很有用,假如你有连续两个NOP指令可以用JMP到下一个指令的方式代替,因为这样可以减少一个指令BYTE,又可以达到相同的效果。
例如:
NOP
NOP
可以写成:
JMP NEXT_INST
NEXT_INST:
;....
因为一个NOP花费一个指令Cycle,但是一个JMP指令就需要2个指令Cycle,虽然有时候会抱怨JMP指令会多花一点时间,但是想不到它也有如此妙用吧。
=========================================
析:
我的做法是定义一个预处理宏:
#define nop2 JMP $+1
需要延时2个指令周期时,用NOP2指令就行了。
对EMC、HOLTEK都适用,但是对NTK4位单片机无效,因为NTK所有指令都是单周期的。
注:$表示指令所在PC地址


pheavecn 赏析3:


引:
交换两组缓存器的内容
如果你觉得要交换两组内存的内容一定要借用第三组变量,那么您可以参考以下的方式,只是用了一些数学技巧就变得又快又简单。
MOV A,REG1
SUB A,REG2
ADD REG1,A
SUB REG2,A
原理说明
A=REG1
A=REG2-REG1
REG1=REG1+A
=REG1+(REG2-REG1)
=REG2
REG2=REG2-(REG2-REG1)
=REG1
若X>Y就交换...
延续上一个例子,此法用应用在Bubble Sort特别管用。
MOV A,X
SUB A,Y
JBC PSW,FC
JMP NO_CHANGE
ADD X,A
SUB Y,A
==================================
析:
这个技巧相对来说使用频率比较少,而且通用性比较差。
对HOLTEK芯片需要更多指令周期:
mov a,Reg1
sub a,Reg2 ;a=reg1-reg2(与EMC指令不同)
addm a,Reg2 ;reg2=reg2+reg1-reg2=reg1(先实现从reg1到reg2)
subm a,Reg1 ;reg1=a-reg1=(reg1-reg2)-reg1=-reg2
cpl reg1
inc reg1 ;这两句实现reg1=-(-reg2)=reg2
由于减法指令的不同,HOLTEK需要6条指令。

pheavecn 赏析4:

引:
LABEL太多?
写汇编语言最令人伤脑筋的问题之一就是程序中到处是label,这有两个坏处,第一就是不小心就会造成label重复的问题,第二就是想不出适当的label名称。如果您已经为label的命名问题肠枯思竭,给您提供一个小方法,程序中如果用「$」可以表示目前PC的地址,依此推论「$+2」表示PC+2,「$-4」表示PC-4,看看底下的例子您立刻就明白:
MOV R,R
JBS PSW,FZ
JMP $+2
JMP $-4
; ....
不过也要给您一个建议,label有个重要的意义就是具有批注的功能,特别是针对一些懒的写批注的人格外重要。所以这个方法仅适合使用在重复性很高的程序片断。
================================
析:
这个问题对汇编程序的可读性影响很大。有经验的汇编程序员都应该很好的应用这个符号。掌握使用$的尺度,需要一段时间。
我的原则是:1、必须是RISC单字指令的指令系统,对51系列不适用。
2、跳转是局部算法内跳转,即本小段程序在整体程序中可以视作不可分割。
3、小段程序不超过一屏,即在屏幕上可以看到整段程序。

pheavecn 赏析5:
引:
计算一个BYTE中有多少个"1"
这个小程序可以检查出在某个BYTE中共有几个1,在某些算法的过程可能会用得到,计算的结果放在ACC。
RRCA DATA
AND A,@0x55
SUB DATA,A
MOV A,DATA
AND A,@0x33
ADD DATA,A
RRC DATA
ADD DATA,A
RRC DATA
SWAPA DATA
ADD A,DATA
AND A,@0x0F
===================
析:
计算一个字节有多少个1,我想不出有什么用处,但是可以做通讯时的奇偶校验。
用HOLTEK语言要改成下面的写法:
mov a,data
and a,55h
adcm a,data
rrc data
mov a,data
and a,33h
addm a,data
rr data
addm a,data
rr data
swapa data
add a,data
and a,0fh
结束时acc中即是原DATA中1的个数。
如果只是奇偶校验,应该还可以简化.


high 关于$:

$还可以解决宏的复用.
当宏里使用到LABLE时,这个宏就只能用一次.
想让宏多次使用,解决办法就是用$.

看《匠人手记》,与匠人同行!北航出版,正在热卖!

发表评论:
载入中...

芯片专题

器件专题

软件专题

硬件专题

综合专题

项目专题

原创专题

器件检测
LCD LED
按键 触摸键
E2PROM
电池 电机
电阻 电容 电感

指令系统
软件算法
编程规范
滤波算法
串行通讯

PCB设计
I2C PWM
红外遥控
充电技术
中断 ADC 

匠人手记
匠人夜话
网络心路
一周热点串烧
从零开始玩PIC
DIY旋转时钟

广告5号位 [投放]


学习板、开发板、编程器、下载器、仿真器(查看详情……)

广告3号位 [投放]

站内搜索


站外搜索


百度  google
mp3  歌词 
图片  FLASH 
知道  文档
新闻  词典 
地图  mp3 
软件  天网 
雅虎  爱问 
搜狗  讯雷 
网讯  华军 
天空 

21IC器件搜索
百宝箱分站
  • 《匠人的百宝箱》21IC站
  • 《匠人的百宝箱》21IC笔记团队
  • 《匠人手记》21IC书友会
  • 《匠人的百宝箱》MCUBLOG站
  • 《匠人的百宝箱》MCUBLOG笔记团队
  • 《匠人的百宝箱》EDN站
  • 《匠人手记》EDN书友会
  • 《匠人的百宝箱》与非网站
  • 《匠人的百宝箱》新浪站
  • 《匠人的百宝箱》百度站
  • 《匠人的百宝箱》网易126站
  • 《匠人的百宝箱》网易163站
  • 《匠人的百宝箱》互动出版网站
  • 广告4号位 [投放]

     
     

    匠人原创

    往日酷贴

     
     
     

    大千八卦

    友情连接

    新浪新闻:
    新浪财经:
    AK58新闻:
    新浪股票:
    新浪股票:
    证券之星:

     [更多酷站连接]

     

     

    [欢迎交换连接]

    [百宝箱之与非门分舵]

    [电脑圈圈的家当]

    [IC921的博客]

    [柔月阁]

    [八楼的呼吸]

    [hotpower 的水潭]

    [xwj的文君阁]

    [所长的BLOG]

    [阿摆手记]

    [电子伙伴]

    [unaided的笔记]

    [小飞的笔记]

    [单片机开发联盟]

    [网址之家]

    [好东西网址大全]

    [美萍中文精选]

    [数字电视之家]

    [SMARTCODE电子书斋]

    [软件开发之窗]

    [Armoric]

    [我爱研发网]

    [infernal的笔记]

    [雄鹰的空中加油站]

    [SunK]

    [逍遥电子]

    [ningpanda的博客]

    [C-Design]

    [一网见天下]

    [海边淘沙]

    [嵌入式365]

    [水牛的仓库]

    [股剩是怎样炼成的]

    [PIC论坛]

    [ICC AVR开发网]

    [中国高校自动化网]

     

     

     

    MCU博客-中国电子工程师博客网 

    大学生电子网 

     

     

     

     

     

    !!! 《匠人的百宝箱》 !!!