一、 实验要求

  • 用汇编实现冒泡排序算法。
  • 在上一个实验(循环程序的设计)的代码基础上,利用冒泡排序将DEST中数据从小到大重新排序 。
  • 将冒泡排序算法写成子程序。
  • 在主程序中,通过设置参数,使用同一个子程序实现从大到小,从小到大排序的自由选择。

二、 实验设计

2.1 整体思路

  • 在main里面设置7AH地址的值,调用子程序来进行冒泡排序

  • 进行现场保护

  • 进入冒泡排序,根据7AH地址的值来判断是从小到大排序还是从大到小排序。

  • 每次取DEST(DPTR指向此地址)为起始地址的数组值,两两比较排序,直到所有的数据都排序完成。

  • 进行现场保护

  • 结束排序,返回main。

2.2 流程图

上流程图省略了从小到大,其过程与从大到小差不多。

2.3 主要模块设计思路及分析

(1)冒泡排序算法模块(这里以从大到小排序为例)

   冒泡算法的的思路(这里以从大到小排序为例)为:

在一组数据中,从第一个数据开始,两两进行比较,如果前者较后者小,则交换他们的位置,如果前者较后者大,则进行下一组的比较。这样每一轮排序之后,我们都可以将一组数据中最小的数据移到最后。下一轮对比的数据则可以去除上一轮的最小数据(每一轮之后,对比的数据减1)。直到所有的数据对比完毕。

在这个程序中,我们需要设计两个循环(外循环与内循环)。

外循环控制整个排序过程需要进行的遍历次数。

对于一个长度为 n 的数组,最多需要进行 n-1 次遍历(因为每完成一次遍历,最小的元素就会被移动到数组的末尾,因此下一次遍历不需要再考虑这个元素)。所以在这里,外循环的初始值为num-1。每循环一次,次数减1。

内循环负责比较并交换相邻的元素。

内循环的起始索引从数组的第一个元素开始,而终止索引则随着外循环的进行逐渐减小(因为每次外循环后,末尾的元素已经排好序,不需要再次比较)。

       所以在这里,内循环的初始值为当前外循环次数的值。每循环一次,次数减1。当内循环结束          后,内循环次数再次赋值为当前外循环次数的值。

(2)利用DPTR取数据模块

        首先将DPTE指向DEST所在的地址。每次需要取两个数据放到两个寄存器中,所以每一次取数据结束后DPTR会指向后一个数据的地址。但是如果这两个数据需要交换的话,则需要先将当前DPTR所指的地址数据改为前一个数据(利用暂存值的A寄存器来实现),再将DPTR的值减一(通过DEC DPL来解决,因为这里不会借位,所以不考虑借位的情况),来将后一个数据写到前一个数据的地址。每进行一轮内循环,DPTR值加一。每进行一次外循环,DPTE重新指向DEST所在的地址。

              

三、 实现效果

a.从小到大排序:

(1)未排序前:

如图可以看到:

在实验三中我们将数据拷贝到2000H为起始地址的RAM片外区。并且将字符‘$’存到最后。

(2)排序后:

如图可以看到:

数据从小到大排序后保存到2000H的起始地址中。

b.从大到小:

(1)未排序前:

如图可以看到:

数据还保存着上一步的从小到大的排序。

(2)排序后:

如图可以看到:

数据从大到小排序后保存到2000H的起始地址中。

四、源代码

//1.用汇编实现冒泡排序算法。
//2.在实验三的代码基础上,利用冒泡排序将DEST中数据从小到大重新排序 。
//3.将冒泡排序算法写成子程序。在主程序中,通过设置参数,使用同一个子程序实现从大到小,从小到大排序的自由选择。
    ORG 0000H  
    LJMP MAIN  
  
    ORG 2000H    
MAIN: 
 	SRC DATA 30H  
	DEST XDATA 2000H
    NUM DATA 20H

	MOV A, #0AH       ; 将立即数0AH加载到累加器A中  ,为需要循环的次数   
    MOV R0, #NUM      ; 将NUM(实际上是20H)加载到寄存器R0中  
    MOV @R0, A    
  
    ACALL WRITE_TO_SRC    ; 调用写入子程序  
    ACALL COPY_TO_DEST  ; 调用复制子程序  

// 进入冒泡排序
	CLR		7AH			;    从小到大排序
	ACALL	ORDERS		;	冒泡排序子程序
	SETB	7AH 		;	从大到小排序
	ACALL	ORDERS    ;	冒泡排序子程序
  
	SJMP	$

   
// 子程序:向SRC写入NUM个字节并以'$'字符结尾  
WRITE_TO_SRC: 
	PUSH PSW
	PUSH Acc
 
    MOV R0, #SRC        ; 将SRC地址加载到R0寄存器  
    MOV R1, NUM         ; 将NUM值加载到R1寄存器 ,为需要循环的次数 ,10次 
    MOV A, #01H 		;数值为1开始写入

	JZ WRITE_FINISH       ; 如果NUM为0,则跳转到WRITE_FINISH
  
WRITE_LOOP:    

	MOV @R0, A			  ;将A的值存入SRC地址中
	INC R0				  ;地址加一
    INC A                 ; 示例:每次循环增加A的值(可以替换为其他逻辑)  
    DJNZ R1, WRITE_LOOP   ; 减1并判断R1(循环次数)是否为0,不为0则继续循环 
  
WRITE_FINISH:  
	MOV A, #'$'
    MOV @R0, A          ; 将'$'字符存储在最后 

	POP Acc
	POP PSW
	 
    RET  
  
// 子程序:将SRC中的数据逆序复制到DEST并以'$'字符结尾  
COPY_TO_DEST:
	PUSH PSW
	PUSH Acc
  
    MOV R0, #SRC         ; 将SRC地址加载到R0寄存器
	MOV R1, NUM  		 ; 将NUM值加载到R1寄存器
    MOV A, NUM         
	ADD A, R0
	ADDC A, #-1
	MOV R0, A  
    MOV DPTR, #DEST     ; 将DEST地址加载到DPTR(用于片外数据存储器访问)  
  
COPY_LOOP:  
	MOV A, @R0  
    MOVX @DPTR, A       ; 将A的内容写入到DEST地址
    INC DPTR            ; DEST地址加1  
    DEC R0              ; SRC地址减1(逆序)  
    DJNZ R1, COPY_LOOP  ; 减1并判断R1是否为0,不为0则继续循环  
  
    ; 写入'$'字符到DEST的末尾 
COPY_FINISH: 
	MOV A, #'$' 
	MOVX @DPTR, A
  
    POP Acc
	POP PSW 

	RET


// 子程序:冒泡排序
ORDERS:
	PUSH PSW		  ;先进行现场保护
	PUSH Acc

	MOV R0, NUM		;R0为外循环次数的初始值(原本应该是NUM-1,但是在上面程序中,其实我们多存入了“$”字符)

	JB	7AH,BIG_TO_SMALL		  ;7AH为1则跳转(从大到小排列)

SMALL_TO_BIG:

OUTER_LOOP1:
   MOV DPTR, #DEST
   MOV A, R0
   MOV R1, A	   ;R1为内循环的次数

INNER_LOOP1:
   MOVX A, @DPTR
   MOV R2, A
   INC DPTR
   MOVX A, @DPTR
   MOV R3, A

   SUBB A, R2
   JC TO_SWAP1			 ;CY=1(R3<R2),则需要转换
   SJMP NO_SWAP1

TO_SWAP1:
   MOV A, R2
   MOVX @DPTR, A
   DEC DPL
   MOV A, R3
   MOVX @DPTR, A

NO_SWAP1:
   INC DPTR				  ;每次内循环结束,dptr值加一
   DJNZ R1, INNER_LOOP1   ;每次内循环结束,内循环次数值减一,不为0则继续内层循环

   DJNZ R0,	OUTER_LOOP1	  ;每次外循环结束,外循环次数值减一,不为0则继续外层循环 

//排序结束,跳转到结束
   SJMP ORDER_FINISH


//从大到小排序
BIG_TO_SMALL:

OUTER_LOOP2:
   MOV DPTR, #DEST
   MOV A, R0
   MOV R1, A

INNER_LOOP2:
   MOVX A, @DPTR
   MOV R2, A
   INC DPTR
   MOVX A, @DPTR
   MOV R3, A

   SUBB A, R2
   JNC TO_SWAP2			 ;CY=0(R3>R2),则需要转换
   SJMP NO_SWAP2

TO_SWAP2:
   MOV A, R2
   MOVX @DPTR, A
   DEC DPL
   MOV A, R3
   MOVX @DPTR, A

NO_SWAP2:
   INC DPTR
   DJNZ R1, INNER_LOOP2 

   DJNZ R0,	OUTER_LOOP2
   
ORDER_FINISH:

	POP Acc
	POP PSW
	
	RET
 

	END

五、总结和思考  

1. 指针操作
   由于 `DEC DPTR` 指令不存在,因此需要手动调整 `DPL` 来实现指针回退。例如:  
 

DEC DPL  ; DPTR = DPTR - 1


   这种方法适用于不会发生借位的情况(即 `DPL` 不为 `00H` 时)。如果涉及跨页调整,则需要同时修改 `DPH` 和 `DPL`。  

2. 寻址方式  
  -直接寻址(如 `MOV A, R0`):操作的是寄存器本身的值。  
   间接寻址(如 `MOVX A, @DPTR`):访问的是指针指向的内存数据。  
   立即寻址(如 `MOV A, #30H`):直接使用常数,不涉及内存或寄存器内容。  

3. 排序逻辑优化
   -通过标志位(如 `7AH`)控制排序方向(升序/降序),减少重复代码。  
   -内层循环次数随外层循环递减,避免不必要的比较。  

4. 调试经验 
   在片外 RAM(`XDATA`)操作时,必须使用 `MOVX` 指令,否则数据无法正确读写。  
   排序前需确保数据范围正确,避免 `'$'` 等非数值字符干扰比较逻辑。  

这些细节在实现排序算法时尤为重要,稍有不慎就会导致结果错误。后续可以尝试更高效的排序算法(如快速排序),并对比其性能差异。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐