单片机/汇编 学习笔记(跳转指令与机器周期)

题目

今天上课讲了汇编的条件跳转指令,然后老师出了一个例题,利用跳转指令和 NOP 指令实现延时。

指令介绍

ORG 指令

ORG 指令用于指定程序或数据在内存中的起始地址。当汇编器开始处理源代码时,ORG 指令告诉它从哪个地址开始放置代码或数据。例如,ORG 100h 表示程序的起始地址为内存地址 100h

MOV 指令

MOV 指令用于数据传输。它将数据从一个位置移动到另一个位置,但不影响源数据。例如,MOV AX, BXBX 寄存器的内容复制到 AX 寄存器,但不会改变 BX 寄存器的内容。

DJNZ 指令

DJNZ(递减并跳转如果不为零)指令通常用于循环结构中。它先将指定寄存器的值减一,然后检查结果是否为零。如果结果不为零,程序跳转到指定的标签或地址。例如,DJNZ BX, LABELBX 寄存器的值减一,如果减完后 BX 不为零,则跳转到 LABEL

NOP 指令

NOP 指令代表"无操作"(No Operation)。在大多数汇编语言中,这个指令的作用就是不执行任何操作。它占用一个指令周期,但不会改变任何寄存器或内存中的值。NOP 指令通常用来产生微小的延迟,或者作为代码中的占位符。

RET 指令

RET 指令用于从子程序返回。当执行到 RET 指令时,程序控制返回到调用子程序的下一条指令。这通常涉及弹出之前压入堆栈的返回地址。


例题中给出了晶振周期 12MHz 和 50ms 的延时,根据机器周期 T = 12/f 可得出单个机器周期为 1µs,然后 50ms/1µs = 50000,即需要延时 50000 个机器周期。

老师利用各指令的特性和占用机器周期数,通过循环和插入 NOP 指令来逼近题目要求的 50000T。

然后老师改了下题目,作为这节课的课后作业,并特别强调了要求的误差:"一般的同学只要能做到小于等于 1% 就行,优秀一点的同学可以尝试做到 0.3% 的误差。"

简单计算一下,f = 6MHz, t = 1min -> T = 30000000 (计算过程略)

要求 30000000 (3e7)个机器周期,考虑到寄存器是 8 位的,最大值是 255,那么应该需要三四层循环嵌套才能做到这么长的延时,要做到 0.3% 的误差似乎没那么容易。

但既然我发了这篇笔记,那么说明我已经做到了 0.3% 以下的误差(自豪脸)

理论分析

首先要明确多重循环和机器周期数的关系.

设 N 表示 N 重循环,Tn 表示在 N 重循环下需要的机器周期数,Rn 为第 N 重循环的寄存器初值(即循环次数)。

首先,如果没有循环,那么就只有一个 RET 语句,占用 2 机器周期,即 T₀ = 2

接着分析单层循环的情况, 有如下代码

此时 N = 1, R₁ 为寄存器初值

设循环内部代码消耗 i₁ 个机器周期, 则机器周期 T₁ = 1 + (i₁ + 2) * R₁ + 2

如果仅有单层循环, 则内部代码为空, 即 i₁ = 0, 则 T₁ = 1 + 2 * R₁ + 2

如果内部代码也是一个循环, 则 i₁ -> 1 + (i₂ + 2) * R₂

则双重循环下的机器周期 T₂ = 1 + (1 + (i₂ + 2) * R₂ + 2) * R₁ + 2

不难得出三重循环的机器周期 T₃ = 1 + (1 + (1 + (i₃ + 2) * R₃ + 2) * R₂ + 2) * R₁ + 2

整理一下有

T₁ = 2 * R₁ + 3

T₂ = (2 * R₂ + 3) * R₁ + 3

T₃ = ((2 * R₃ + 3) * R₂ + 3) * R₁ + 3

看起来很像某种递推函数, 观察易得
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> T n = { 2 if n = 0 T n − 1 ⋅ R n + 3 otherwise T_n = \begin{cases} 2 & \text{if } n=0 \\ T_{n-1} \cdot R_n + 3 & \text{otherwise} \end{cases} </math>Tn={2Tn−1⋅Rn+3if n=0otherwise

根据函数可以写出对应的C语言代码

c 复制代码
double calc(int *arr, int *end) {
    return arr == end ? 2 : calc(arr + 1, end) * *arr + 3;
}

暴力搜索

循环计数器 RN 的取值范围为 {1, 2, 3, ..., 255, 256} (256 可以通过赋值 #0 来实现)

有 N 重循环就是求这个集合的 N 重笛卡尔积, 然后一一计算每个元素与目标 T 的 delta 误差值

遍历的部分代码如下

c 复制代码
void traverse(int arr[], int index) {
    if (index == N) {
        check(arr);
    } else {
        for (int i = MIN; i <= MAX; i++) {
            arr[index] = i;
            traverse(arr, index + 1);
        }
    }
}

经过代码的计算, 可以得出在三重循环时, 循环计数初值分别为 [253, 255, 231] 时,与目标 30000000 个机器周期的误差为 0.0007899937590493036 %

而在四重循环时, 有如图所示的 61 组解可以达到完美的精度(0 误差)

汇编代码生成

观察例题代码, 发现除了数值之外, 多重循环的汇编实现也具有一定的规律, 因此可以使用模板字符串写一个代码生成器, 下面我将使用我日常使用较多的脚本语言 JavaScript 来写

JavaScript 复制代码
function asmCodeGenerate(arr) {
    const movs = arr.map((e, i) => `\tMOV R${i},#${e}`)
    const djnzs = arr.map((_, i) => `\tDJNZ R${i},LOOP${i}`).reverse()

    const mds = [...movs, ...djnzs]
    for (let i = 1; i <= arr.length; ++i) {
        mds[i] = `LOOP${i - 1}:` + mds[i]
    }

    return ['\tORG 7100H', ...mds, '\tRET'].join('\n')
}

console.log(asmCodeGenerate([239, 240, 104, 1]))

生成代码:

asm 复制代码
        ORG 7100H
        MOV R0,#239
LOOP0:  MOV R1,#240
LOOP1:  MOV R2,#104
LOOP2:  MOV R3,#1
LOOP3:  DJNZ R3,LOOP3
        DJNZ R2,LOOP2
        DJNZ R1,LOOP1
        DJNZ R0,LOOP0
        RET

🎉完结撒花!交作业!

(如有错误和遗漏请指正 QAQ)

相关推荐
Crossoads3 天前
【汇编语言】[BX]和loop指令(四)—— 汇编语言中的段前缀与内存保护:原理与应用解析
android·java·开发语言·数据库·机器学习·汇编语言
CYRUS_STUDIO5 天前
Android下的系统调用 (syscall),内联汇编syscall
android·linux·汇编语言
我不是程序猿儿5 天前
【数据结构】汇编语言和机器语言的‘数据结构‘
开发语言·数据结构·汇编语言·机器语言
CYRUS_STUDIO6 天前
Android 下内联汇编,Android Studio 汇编开发
android·汇编语言
Crossoads17 天前
【汇编语言】第一个程序(三)—— 深度剖析汇编程序的执行流程:编辑、编译、连接与运行
android·java·开发语言·数据库·网络协议·机器学习·汇编语言
xuan哈哈哈19 天前
汇编语言
网络安全·汇编语言
Crossoads22 天前
【汇编语言】第三章----寄存器(内存访问)(八)—— 栈顶越界的问题
java·开发语言·数据库·网络协议·tcp/ip·机器学习·汇编语言
Crossoads1 个月前
【汇编语言】寄存器(内存访问)(七)—— CPU提供的栈机制
android·java·tcp/ip·机器学习·cpu··汇编语言
Crossoads1 个月前
【汇编语言】寄存器(内存访问)(二)—— DS和[address]
android·java·tcp/ip·机器学习·汇编语言
Crossoads1 个月前
【汇编语言】寄存器(CPU工作原理)(六)—— 修改CS,IP的指令以及代码段
android·开发语言·网络协议·tcp/ip·机器学习·汇编语言