引言
typedef 声明,简称 typedef,为现有类型创建一个新的名字。比如人们常常使用 typedef 来编写更美观和可读的代码。所谓美观,意指 typedef 能隐藏笨拙的语法构造以及平台相关的数据类型,从而增强可移植性和以及未来的可维护性。 在编程中使用typedef目的一般有两个,一个是给变量一个易记且意义明确的新名字,另一个是简化一些比较复杂的类型声明。
typedef的使用方法如下:
typedef existing_type new_type_name ;
注意:typedef 并不创建新的类型。它仅仅为现有类型添加一个同义字。
typedef的最简单使用
typedef int size; typedef unsigned int WORD;
第一个声明定义了一个char的同义词,名字为C,你可以在任何需要int的上下文使用size。 第二个声明定义了一个unsigned int的同义词,名字为WORD,你可以在任何需要int的上下文使用WORD。
typedef和数组,指针
我们可以不用象下面这样重复定义有 81 个字符元素的数组:
char line[81]; char text[81];
定义一个 typedef,每当要用到相同类型和大小的数组时,可以这样:
typedef char Line[81]; Line text, secondline;
同样,可以象下面这样隐藏指针语法:
typedef char * pstr; pstr str = "abc"; int mystrcmp(pstr, pstr);
typedef和函数
函数指针一般用于回调,例如信号处理,libcurl等会应用到回调。回调是比较常用的技术,而回调就要涉及函数指针。 当我们的程序中有以下函数:
void printHello(int i);
然后我们要定义一个函数指针,指向printHello,并且调用这个方法,代码如下:
void (*pFunc)(int); pFunc = &printHello; (*pFunc)(110);
其中void (*pFunc)(int)是声明一个函数指针,指向返回值是void,调用参数是(int)的函数,变量名是pFunc,pFunc就是函数指针了,以前是函数指针的简单用法。 大家可以看到,声明一个函数指针是比较复杂的,尤其是当你要在多处地方声明同一个类型的函数指针变量,代码更加复杂,所以有下面简化的做法:
typedef void (*PrintHelloHandle)(int);
使用代码如下:
PrintHelloHandle pFunc; pFunc = &printHello; (*pFunc)(110);
以后其他地方的程序需要声明类似的函数指针,只需要下面代码:
PrintHelloHandle pFuncOther;
这样,我们的代码就变得更加简洁易懂。
typedef心得
大家在看typedef和数组,指针以及typedef和函数,可能会觉得typedef比较复杂,但是其实typedef 行为有点像 #define 宏,用其实际类型替代同义字。 请看看下面例子
typedef char * pstr; pstr mystr;
代码pstr mystr,展开之后就是char * mystr,把mystr,替换为语句typedef char * pstr的pstr,展开之后还是char * mystr。 typedef并不创建新的类型,typedef 在编译时被解释,因此让编译器来应付超越预处理器能力的文本替换而已。 这些规则可以应用到typedef和数组:
typedef char Line[81]; Line text;
把text替换为typedef char Line[81]的Line,展开之后就是
char text[81];
也可以应用到最复杂的typedef和指针
typedef void (*PrintHelloHandle)(int); PrintHelloHandle pFunc;
将pFunc替换typedef void (*PrintHelloHandle)(int),展开之后就是
void (*pFunc)(int);
BS_OEMName DB 'ForrestY' ; OEM String, 必须 8 个字节
BPB_BytsPerSec DW 512 ; 每扇区字节数
BPB_SecPerClus DB 1 ; 每簇多少扇区
BPB_RsvdSecCnt DW 1 ; Boot 记录占用多少扇区
BPB_NumFATs DB 2 ; 共有多少 FAT 表
BPB_RootEntCnt DW 224 ; 根目录文件数最大值
BPB_TotSec16 DW 2880 ; 逻辑扇区总数
BPB_Media DB 0xF0 ; 媒体描述符
BPB_FATSz16 DW 9 ; 每FAT扇区数
BPB_SecPerTrk DW 18 ; 每磁道扇区数
BPB_NumHeads DW 2 ; 磁头数(面数)
BPB_HiddSec DD 0 ; 隐藏扇区数
BPB_TotSec32 DD 0 ; wTotalSectorCount为0时这个值记录扇区数
BS_DrvNum DB 0 ; 中断 13 的驱动器号
BS_Reserved1 DB 0 ; 未使用
BS_BootSig DB 29h ; 扩展引导标记 (29h)
BS_VolID DD 0 ; 卷序列号
BS_VolLab DB 'OrangeS0.02'; 卷标, 必须 11 个字节
BS_FileSysType DB 'FAT12 ' ; 文件系统类型, 必须 8个字节
读软盘 中断13h
------------------------------------------------------------------------------------------
中断号 | 寄存器 | 作用 |
------------------------------------------------------------------------------------------
|ah=00h dl=驱动器号(0表示A盘) | 复位软驱 |
|---------------------------------------------------------------------------
|ah=02h al=要读取的扇区数 |从磁盘将数据读 |
13h |ch=柱面(磁道)号 cl=起始扇区号 |入es:bx指向的 |
|dh=磁头号 dl=驱动器号(0表示A盘) |缓冲区 |
|es:bx->数据缓冲区 | |
-----------------------------------------------------------------------------------------
BPB参数
好了,接下来重点来了。首先,最开始的3各字节的数据分别是跳转指令与空指令,因为在汇编当中0xEB是跳转指令,0x58是跳转的地址,而0x90则是空指令。至于为什么要在这里放上一句跳转指令呢,这个还得从启动区开始讲起,为了节约篇幅,我就简单介绍一下:一般第一个扇区叫做启动区,cpu把扇区当中的数据当作指令来执行,当读取到EB 58 这个指令时,遍跳转到0x58这个地址并继续读取指令来执行,而0x58地址之后的内容通常都是载入操作系统的指令。如果希望知道详细内容的读者不妨去看一下《30天自制操作系统》这本书,第一天结尾部分有很详细的说明。总之这边的话FAT32规定这个3各字节的内容必须是EB 58 90,只要记住就行了(笑)。(如1L所说,EB 58 90 对应汇编代码即为JUMP 0x58; NOP;)。
而从0x03~0x0A这8个字节的数据表示OEM,这里即为“MSDOS5.0”。
Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释
显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。
变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“\#”。
最后,还值得一提的是,在Makefile中的命令,必须要以[Tab]键开始。
= 赋值,可以引用后面变量 例子:
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:
echo $(foo)
我们执行“make all”将会打出变量$(foo)的值是“Huh?”( $(foo)的值是$(bar),$(bar)的值是$(ugh),$(ugh)的值是“Huh?”)可见,变量是可以使用后面的变量来定义的。
:= 赋值,只能引用前面变量
?= 例子:
FOO ?= bar
其含义是,如果FOO没有被定义过,那么变量FOO的值就是“bar”,如果FOO先前被定义过,那么这条语将什么也不做,其等价于:
三、变量高级用法:
第一种是变量值的替换。
示例:
foo := a.o b.o c.o
bar := $(foo:.o=.c)
这个示例中,我们先定义了一个“$(foo)”变量,而第二行的意思是把“$(foo)”中所有以“.o”字串“结尾”全部替换成“.c”,所以我们的“$(bar)”的值就是“a.c b.c c.c”。
另外一种变量替换的技术是以“静态模式”(参见前面章节)定义的,如:
oo := a.o b.o c.o
bar := $(foo:%.o=%.c)
这依赖于被替换字串中的有相同的模式,模式中必须包含一个“%”字符,这个例子同样让$(bar)变量的值为“a.c b.c c.c”。
第二种高级用法是——“把变量的值再当成变量”
x = y
y = z
a := $($(x))
在这个例子中,$(x)的值是“y”,所以$($(x))就是$(y),于是$(a)的值就是“z”。(注意,是“x=y”,而不是“x=$(y)”)
四、追加变量值:
+=如:
objects = main.o foo.o bar.o utils.o
objects += another.o
于是,我们的$(objects)值变成:“main.o foo.o bar.o utils.o another.o”(another.o被追加进去了)
五、override 指示符:
如果有变量是通常make的命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果你想在Makefile中设置这类参数的值,那么,你可以使用“override”指示符。其语法是:
override <variable> = <value>
override <variable> := <value>
当然,你还可以追加:
override <variable> += <more text>
对于多行的变量定义,我们用define指示符,在define指示符前,也同样可以使用ovveride指示符,如:
override define foo
bar
endef
六、多行变量
还有一种设置变量值的方法是使用define关键字。使用define关键字设置变量的值可以有换行,这有利于定义一系列的命令(前面我们讲过“命令包”的技术就是利用这个关键字)
define 指示符后面跟的是变量的名字,而重起一行定义变量的值,定义是以endef关键字结束。其工作方式和“=”操作符一样。变量的值可以包含函数、命令、文字,或是其它变量。因为命令需要以[Tab]键开头,所以如果你用define定义的命令变量中没有以[Tab]键开头,那么make就不会把其认为是命令。
下面的这个示例展示了define的用法:
define two-lines
echo foo
echo $(bar)
endef
七、环境变量
make 运行时的系统环境变量可以在make开始运行时被载入到Makefile文件中,但是如果Makefile中已定义了这个变量,或是这个变量由make命令行带入,那么系统的环境变量的值将被覆盖。(如果make指定了“-e”参数,那么,系统环境变量将覆盖Makefile中定义的变量)
八、目标变量
前面我们所讲的在Makefile中定义的变量都是“全局变量”,在整个文件,我们都可以访问这些变量。当然,“自动化变量”除外,如“$<”等这种类量的自动化变量就属于“规则型变量”,这种变量的值依赖于规则的目标和依赖目标的定义。
用法:
<target ...> : <variable-assignment>
<target ...> : overide <variable-assignment>
如:
prog : CFLAGS = -g
prog : prog.o foo.o bar.o
$(CC) $(CFLAGS) prog.o foo.o bar.o
九、模式变量
make的“模式”一般是至少含有一个“%”的
模式变量的语法和“目标变量”一样:
<pattern ...> : <variable-assignment>
<pattern ...> : override <variable-assignment>
使用条件判断
——————
一、示例
下面的例子,判断$(CC)变量是否“gcc”,如果是的话,则使用GNU函数编译目标。
libs_for_gcc = -lgnu
normal_libs =
foo: $(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif
二、语法
条件表达式的语法为:
<conditional-directive>
<text-if-true>
endif
以及:
<conditional-directive>
<text-if-true>
else
<text-if-false>
endif
其中<conditional-directive>表示条件关键字
第一个是“ifeq” 比较参数“arg1”和“arg2”的值是否相同。
第二个是“ifneq” 其比较参数“arg1”和“arg2”的值是否相同,如果不同,则为真。和“ifeq”类似。
第三个是“ifdef” 如果变量<variable-name>的值非空,那到表达式为真。否则,表达式为假。
第四个是“ifndef” 和“ifdef”是相反的意思。
使用函数
————
一、函数的调用语法
函数调用,很像变量的使用,也是以“$”来标识的,其语法如下:
$(<function> <arguments> )
或是
${<function> <arguments>}
这里,<function>就是函数名,make支持的函数不多。<arguments>是函数的参数,参数间以逗号“,”分隔,而函数名和参数之间以“空格”分隔。函数调用以“$”开头,以圆括号或花括号把函数名和参数括起。感觉很像一个变量,是不是?函数中的参数可以使用变量,为了风格的统一,函数和变量的括号最好一样,如使用“$(subst a,b,$(x))”这样的形式,而不是“$(subst a,b,${x})”的形式。因为统一会更清楚,也会减少一些不必要的麻烦。
二、字符串处理函数
$(subst <from>,<to>,<text> )
名称:字串替换函数——subst。
功能:把字串<text>中的<from>字符串替换成<to>。
返回:函数返回被替换过后的字符串。
名称:模式字符串替换函数——patsubst。
功能:查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern>,如果匹配的话,则以<replacement>替换。这里,<pattern>可以包括通配符“%”,表示任意长度的字串。如果<replacement>中也包含“%”,那么,<replacement>中的这个“%”将是<pattern>中的那个“%”所代表的字串。(可以用“\”来转义,以“\%”来表示真实含义的“%”字符)返回:函数返回被替换过后的字符串。
名称:去空格函数——strip。
功能:去掉<string>字串中开头和结尾的空字符。
返回:返回被去掉空格的字符串值。
名称:查找字符串函数——findstring。
功能:在字串<in>中查找<find>字串。
返回:如果找到,那么返回<find>,否则返回空字符串。
名称:过滤函数——filter。
功能:以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词。可
以有多个模式。
返回:返回符合模式<pattern>的字串。
名称:反过滤函数——filter-out。
功能:以<pattern>模式过滤<text>字符串中的单词,去除符合模式<pattern>的单词。可
以有多个模式。
返回:返回不符合模式<pattern>的字串。
名称:排序函数——sort。
功能:给字符串<list>中的单词排序(升序)。
返回:返回排序后的字符串。
示例:$(sort foo bar lose)返回“bar foo lose” 。
名称:取单词函数——word。
功能:取字符串<text>中第<n>个单词。(从一开始)
返回:返回字符串<text>中第<n>个单词。如果<n>比<text>中的单词数要大,那么返回空
字符串。
名称:取单词串函数——wordlist。
功能:从字符串<text>中取从<s>开始到<e>的单词串。<s>和<e>是一个数字。
返回:返回字符串<text>中从<s>到<e>的单词字串。如果<s>比<text>中的单词数要大,那
么返回空字符串。如果<e>大于<text>的单词数,那么返回从<s>开始,到<text>结束的单
词串。
名称:单词个数统计函数——words。
功能:统计<text>中字符串中的单词个数。
返回:返回<text>中的单词数。
示例:$(words, foo bar baz)返回值是“3”。
备注:如果我们要取<text>中最后的一个单词,我们可以这样:$(word $(words <text>
),<text> )。
名称:首单词函数——firstword。
功能:取字符串<text>中的第一个单词。
返回:返回字符串<text>的第一个单词。
示例:$(firstword foo bar)返回值是“foo”。
备注:这个函数可以用word函数来实现:$(word 1,<text> )。
三、文件名操作函数
$(dir <names...> )
名称:取目录函数——dir。
功能:从文件名序列<names>中取出目录部分。目录部分是指最后一个反斜杠(“/”)之
前的部分。如果没有反斜杠,那么返回“./”。
返回:返回文件名序列<names>的目录部分。
示例: $(dir src/foo.c hacks)返回值是“src/ ./”。
名称:取文件函数——notdir。
功能:从文件名序列<names>中取出非目录部分。非目录部分是指最后一个反斜杠(“/”
)之后的部分。
返回:返回文件名序列<names>的非目录部分。
示例: $(notdir src/foo.c hacks)返回值是“foo.c hacks”。
名称:取后缀函数——suffix。
功能:从文件名序列<names>中取出各个文件名的后缀。
返回:返回文件名序列<names>的后缀序列,如果文件没有后缀,则返回空字串。
示例:$(suffix src/foo.c src-1.0/bar.c hacks)返回值是“.c .c”。
名称:取前缀函数——basename。
功能:从文件名序列<names>中取出各个文件名的前缀部分。
返回:返回文件名序列<names>的前缀序列,如果文件没有前缀,则返回空字串。
示例:$(basename src/foo.c src-1.0/bar.c hacks)返回值是“src/foo src-1.0/bar h
acks”。
名称:加后缀函数——addsuffix。
功能:把后缀<suffix>加到<names>中的每个单词后面。
返回:返回加过后缀的文件名序列。
示例:$(addsuffix .c,foo bar)返回值是“foo.c bar.c”。
名称:加前缀函数——addprefix。
功能:把前缀<prefix>加到<names>中的每个单词后面。
返回:返回加过前缀的文件名序列。
示例:$(addprefix src/,foo bar)返回值是“src/foo src/bar”。
名称:连接函数——join。
功能:把<list2>中的单词对应地加到<list1>的单词后面。如果<list1>的单词个数要比<
list2>的多,那么,<list1>中的多出来的单词将保持原样。如果<list2>的单词个数要比
<list1>多,那么,<list2>多出来的单词将被复制到<list2>中。
返回:返回连接过后的字符串。
示例:$(join aaa bbb , 111 222 333)返回值是“aaa111 bbb222 333”。
四、foreach 函数
foreach 函数和别的函数非常的不一样。因为这个函数是用来做循环用的,Makefile中的
foreach函数几乎是仿照于Unix标准Shell(/bin /sh)中的for语句,或是C-Shell(/bin
/csh)中的foreach语句而构建的。它的语法是:
$(foreach <var>,<list>,<text> )
这个函数的意思是,把参数<list>中的单词逐一取出放到参数<var>所指定的变量中,然后再执行<text>所包含的表达式。每一次<text>会返回一个字符串,循环过程中,<text>的所返回的每个字符串会以空格分隔,最后当整个循环结束时,<text>所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。
五、if 函数
if函数很像GNU的make所支持的条件语句——ifeq(参见前面所述的章节),if函数的语法是:
$(if <condition>,<then-part> )
或是
$(if <condition>,<then-part>,<else-part> )
六、call函数
call函数是唯一一个可以用来创建新的参数化的函数。你可以写一个非常复杂的表达式,这个表达式中,你可以定义许多参数,然后你可以用call函数来向这个表达式传递参数。其语法是:
$(call <expression>,<parm1>,<parm2>,<parm3>...)
当 make执行这个函数时,<expression>参数中的变量,如$(1),$(2),$(3)等,会被参数<parm1>,<parm2>,<parm3>依次取代。而<expression>的返回值就是 call函数的返回值。
七、origin函数
注意,<variable>是变量的名字,不应该是引用。所以你最好不要在<variable>中使用“$”字符。Origin函数会以其返回值来告诉你这个变量的“出生情况”,下面,是origin函
数的返回值:
“undefined”
如果<variable>从来没有定义过,origin函数返回这个值“undefined”。
“default”
八、shell函数
shell 函数也不像其它的函数。顾名思义,它的参数应该就是操作系统Shell的命令。它和反引号“`”是相同的功能。这就是说,shell函数把执行操作系统命令后的输出作为函数
返回。于是,我们可以用操作系统命令以及字符串处理命令awk,sed等等命令来生成一个变量,如:
contents := $(shell cat foo)
files := $(shell echo *.c)
注意,这个函数会新生成一个Shell程序来执行命令,所以你要注意其运行性能,如果你的Makefile中有一些比较复杂的规则,并大量使用了这个函数,那么对于你的系统性能是有害的。特别是Makefile的隐晦的规则可能会让你的shell函数执行的次数比你想像的多得多。
九、控制make的函数
make提供了一些函数来控制make的运行。通常,你需要检测一些运行Makefile时的运行时信息,并且根据这些信息来决定,你是让make继续执行,还是停止。
$(error <text ...> )
产生一个致命的错误,<text ...>是错误信息。注意,error函数不会在一被使用就会产生错误信息,所以如果你把其定义在某个变量中,并在后续的脚本中使用这个变量,那么也
是可以的。
make 的运行
一、make的退出码
make命令执行后有三个退出码:
0 —— 表示成功执行。
1 —— 如果make运行时出现任何错误,其返回1。
2 —— 如果你使用了make的“-q”选项,并且make使得一些目标不需要更新,那么返回2。
二、指定Makefile
前面我们说过,GNU make找寻默认的Makefile的规则是在当前目录下依次找三个文件——“GNUmakefile”、“makefile”和“Makefile”。其按顺序找这三个文件,一旦找到,就
开始读取这个文件并执行。
当前,我们也可以给make命令指定一个特殊名字的Makefile。要达到这个功能,我们要使用make的“-f”或是“--file”参数(“-- makefile”参数也行)。
三、指定目标
一般来说,make的最终目标是makefile中的第一个目标,而其它目标一般是由这个目标连带出来的。这是make的默认行为。当然,一般来说,你的 makefile中的第一个目标是由许多个目标组成,你可以指示make,让其完成你所指定的目标。要达到这一目的很简单,需在make命令后直接跟目标的名字就可以完成(如前面提到的“make clean”形式)任何在makefile中的目标都可以被指定成终极目标,但是除了以“- ”打头,或是包含了“=”的目标,因为有这些字符的目标,会被解析成命令行参数或是变量。甚至没有被我们明确写出来的目标也可以成为make的终极目标,也就是说,只要make可以找到其隐含规则推导规则,那么这个隐含目标同样可以被指定成终极目标。
有一个make的环境变量叫“MAKECMDGOALS”,这个变量中会存放你所指定的终极目标的列表,如果在命令行上,你没有指定目标,那么,这个变量是空值。这个变量可以让你使用在一些比较特殊的情形下。
“all” 这个伪目标是所有目标的目标,其功能一般是编译所有的目标。
“clean” 这个伪目标功能是删除所有被make创建的文件。
“install” 这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去。
“print” 这个伪目标的功能是例出改变过的源文件。
“tar” 这个伪目标功能是把源程序打包备份。也就是一个tar文件。
“dist” 这个伪目标功能是创建一个压缩文件,一般是把tar文件压成Z文件。或是gz文件。
“TAGS” 这个伪目标功能是更新所有的目标,以备完整地重编译使用。
“check”和“test” 这两个伪目标一般用来测试makefile的流程。
四、检查规则
有时候,我们不想让我们的makefile中的规则执行起来,我们只想检查一下我们的命令,或是执行的序列。于是我们可以使用make命令的下述参数:
“-n”
“--just-print”
“--dry-run”
“--recon”
不执行参数,这些参数只是打印命令,不管目标是否更新,把规则和连带规则下的命令打印出来,但不执行,这些参数对于我们调试makefile很有用处。
“-t”
“--touch”
这个参数的意思就是把目标文件的时间更新,但不更改目标文件。也就是说,make假装编译目标,但不是真正的编译目标,只是把目标变成已编译过的状态。
“-q”
“--question”
这个参数的行为是找目标的意思,也就是说,如果目标存在,那么其什么也不会输出,当然也不会执行编译,如果目标不存在,其会打印出一条出错信息。
“-W <file>”
“--what-if=<file>”
“--assume-new=<file>”
“--new-file=<file>”
这个参数需要指定一个文件。一般是是源文件(或依赖文件),Make会根据规则推导来运行依赖于这个文件的命令,一般来说,可以和“-n”参数一同使用,来查看这个依赖文件
所发生的规则命令。
另外一个很有意思的用法是结合“-p”和“-v”来输出makefile被执行时的信息
五、make的参数
“-b”
“-m”
这两个参数的作用是忽略和其它版本make的兼容性。
“-B”
“--always-make”
认为所有的目标都需要更新(重编译)。
“-C <dir>”
“--directory=<dir>”
指定读取makefile的目录。如果有多个“-C”参数,make的解释是后面的路径以前面的作为相对路径,并以最后的目录作为被指定目录。如:“make –C ~hchen/test –C prog”
等价于“make –C ~hchen/test/prog”。
“—debug[=<options>]”
输出make的调试信息。它有几种不同的级别可供选择,如果没有参数,那就是输出最简单的调试信息。下面是<options>的取值:
a —— 也就是all,输出所有的调试信息。(会非常的多)
b —— 也就是basic,只输出简单的调试信息。即输出不需要重编译的目标。
v —— 也就是verbose,在b选项的级别之上。输出的信息包括哪个makefile被解析,不需要被重编译的依赖文件(或是依赖目标)等。
i —— 也就是implicit,输出所以的隐含规则。
j —— 也就是jobs,输出执行规则中命令的详细信息,如命令的PID、返回码等。
m —— 也就是makefile,输出make读取makefile,更新makefile,执行makefile的信息。
“-d”
相当于“--debug=a”。
“-e”
“--environment-overrides”
指明环境变量的值覆盖makefile中定义的变量的值。
“-f=<file>”
“--file=<file>”
“--makefile=<file>”
指定需要执行的makefile。
“-h”
“--help”
显示帮助信息。
“-i”
“--ignore-errors”
在执行时忽略所有的错误。
“-I <dir>”
“--include-dir=<dir>”
指定一个被包含makefile的搜索目标。可以使用多个“-I”参数来指定多个目录。
“-j [<jobsnum>]”
“--jobs[=<jobsnum>]”
指同时运行命令的个数。如果没有这个参数,make运行命令时能运行多少就运行多少。如果有一个以上的“-j”参数,那么仅最后一个“-j”才是有效的。(注意这个参数在MS-D
OS中是无用的)
“-k”
“--keep-going”
出错也不停止运行。如果生成一个目标失败了,那么依赖于其上的目标就不会被执行了。
“-l <load>”
“--load-average[=<load]”
“—max-load[=<load>]”
指定make运行命令的负载。
“-n”
“--just-print”
“--dry-run”
“--recon”
仅输出执行过程中的命令序列,但并不执行。
“-o <file>”
“--old-file=<file>”
“--assume-old=<file>”
不重新生成的指定的<file>,即使这个目标的依赖文件新于它。
“-p”
“--print-data-base”
输出makefile中的所有数据,包括所有的规则和变量。这个参数会让一个简单的makefile都会输出一堆信息。如果你只是想输出信息而不想执行 makefile,你可以使用“make -q
p”命令。如果你想查看执行makefile前的预设变量和规则,你可以使用“make –p –f /dev/null”。这个参数输出的信息会包含着你的makefile文件的文件名和行号,所以,用
这个参数来调试你的makefile会是很有用的,特别是当你的环境变量很复杂的时候。
“-q”
“--question”
不运行命令,也不输出。仅仅是检查所指定的目标是否需要更新。如果是0则说明要更新,如果是2则说明有错误发生。
“-r”
“--no-builtin-rules”
禁止make使用任何隐含规则。
“-R”
“--no-builtin-variabes”
禁止make使用任何作用于变量上的隐含规则。
“-s”
“--silent”
“--quiet”
在命令运行时不输出命令的输出。
“-S”
“--no-keep-going”
“--stop”
取消“-k”选项的作用。因为有些时候,make的选项是从环境变量“MAKEFLAGS”中继承下来的。所以你可以在命令行中使用这个参数来让环境变量中的“-k”选项失效。
“-t”
“--touch”
相当于UNIX的touch命令,只是把目标的修改日期变成最新的,也就是阻止生成目标的命令运行。
“-v”
“--version”
输出make程序的版本、版权等关于make的信息。
“-w”
“--print-directory”
输出运行makefile之前和之后的信息。这个参数对于跟踪嵌套式调用make时很有用。
“--no-print-directory”
禁止“-w”选项。
“-W <file>”
“--what-if=<file>”
“--new-file=<file>”
“--assume-file=<file>”
假定目标<file>需要更新,如果和“-n”选项使用,那么这个参数会输出该目标更新时的运行动作。如果没有“-n”那么就像运行UNIX的“touch”命令一样,使得<file>的修改时
间为当前时间。
“--warn-undefined-variables”
只要make发现有未定义的变量,那么就输出警告信息。
隐含规则
————
隐含规则”也就是一种惯例,make会按照这种“惯例”心照不喧地来运行,那怕我们的Makefile中没有书写这样的规则。
一、使用隐含规则
如果要使用隐含规则生成你需要的目标,你所需要做的就是不要写出这个目标的规则。那么,make会试图去自动推导产生这个目标的规则和命令,如果make可以自动推导生成这个目标的规则和命令,那么这个行为就是隐含规则的自动推导。当然,隐含规则是make事先约定好的一些东西。
make 会在自己的“隐含规则”库中寻找可以用的规则,如果找到,那么就会使用。如果找不到,那么就会报错。
还有,在make的“隐含规则库”中,每一条隐含规则都在库中有其顺序,越靠前的则是越被经常使用的,所以,这会导致我们有些时候即使我们显示地指定了目标依赖,make也不会管。
二、隐含规则一览
这里我们将讲述所有预先设置(也就是make内建)的隐含规则,如果我们不明确地写下规则,那么,make就会在这些规则中寻找所需要规则和命令。当然,我们也可以使用make的参数“-r”或“--no-builtin-rules”选项来取消所有的预设置的隐含规则。
当然,即使是我们指定了“-r”参数,某些隐含规则还是会生效,因为有许多的隐含规则都是使用了“后缀规则”来定义的,所以,只要隐含规则中有“后缀列表 ”(也就一系统
定义在目标.SUFFIXES的依赖目标),那么隐含规则就会生效。默认的后缀列表是:.out,.a, .ln, .o, .c, .cc, .C, .p, .f, .F, .r, .y, .l, .s, .S, .mod, .sym, .def, .
h, .info, .dvi, .tex, .texinfo, .texi, .txinfo, .w, .ch .web, .sh, .elc, .el。
三、隐含规则使用的变量
在隐含规则中的命令中,基本上都是使用了一些预先设置的变量。你可以在你的makefile中改变这些变量的值,或是在make的命令行中传入这些值,或是在你的环境变量中设置这些值,无论怎么样,只要设置了这些特定的变量,那么其就会对隐含规则起作用。当然,你也可以利用make的“-R”或“--no– builtin-variables”参数来取消你所定义的变量
对隐含规则的作用。
下面是所有隐含规则中会用到的变量:
1、关于命令的变量。
AR 函数库打包程序。默认命令是“ar”。
AS
汇编语言编译程序。默认命令是“as”。
CC
C语言编译程序。默认命令是“cc”。
CXX
C++语言编译程序。默认命令是“g++”。
CO
从 RCS文件中扩展文件程序。默认命令是“co”。
CPP
C程序的预处理器(输出是标准输出设备)。默认命令是“$(CC) –E”。
FC
Fortran 和 Ratfor 的编译器和预处理程序。默认命令是“f77”。
GET
从SCCS文件中扩展文件的程序。默认命令是“get”。
LEX
Lex方法分析器程序(针对于C或Ratfor)。默认命令是“lex”。
PC
Pascal语言编译程序。默认命令是“pc”。
YACC
Yacc文法分析器(针对于C程序)。默认命令是“yacc”。
YACCR
Yacc文法分析器(针对于Ratfor程序)。默认命令是“yacc –r”。
MAKEINFO
转换Texinfo源文件(.texi)到Info文件程序。默认命令是“makeinfo”。
TEX
从TeX源文件创建TeX DVI文件的程序。默认命令是“tex”。
TEXI2DVI
从Texinfo源文件创建军TeX DVI 文件的程序。默认命令是“texi2dvi”。
WEAVE
转换Web到TeX的程序。默认命令是“weave”。
CWEAVE
转换C Web 到 TeX的程序。默认命令是“cweave”。
TANGLE
转换Web到Pascal语言的程序。默认命令是“tangle”。
CTANGLE
转换C Web 到 C。默认命令是“ctangle”。
RM
删除文件命令。默认命令是“rm –f”。
2、关于命令参数的变量
下面的这些变量都是相关上面的命令的参数。如果没有指明其默认值,那么其默认值都是
空。
ARFLAGS
函数库打包程序AR命令的参数。默认值是“rv”。
ASFLAGS
汇编语言编译器参数。(当明显地调用“.s”或“.S”文件时)。
CFLAGS
C语言编译器参数。
CXXFLAGS
C++语言编译器参数。
COFLAGS
RCS命令参数。
CPPFLAGS
C预处理器参数。( C 和 Fortran 编译器也会用到)。
FFLAGS
Fortran语言编译器参数。
GFLAGS
SCCS “get”程序参数。
LDFLAGS
链接器参数。(如:“ld”)
LFLAGS
Lex文法分析器参数。
PFLAGS
Pascal语言编译器参数。
RFLAGS
Ratfor 程序的Fortran 编译器参数。
YFLAGS
Yacc文法分析器参数。
四、隐含规则链
有些时候,一个目标可能被一系列的隐含规则所作用。例如,一个[.o]的文件生成,可能会是先被Yacc的[.y]文件先成[.c],然后再被C的编译器生成。我们把这一系列的隐含规则
叫做“隐含规则链”
五、定义模式规则
你可以使用模式规则来定义一个隐含规则。一个模式规则就好像一个一般的规则,只是在规则中,目标的定义需要有"%"字符。"%"的意思是表示一个或多个任意字符。在依赖目标中同样可以使用"%",只是依赖目标中的"%"的取值,取决于其目标。
1、模式规则介绍
模式规则中,至少在规则的目标定义中要包含"%",否则,就是一般的规则。目标中的"%"定义表示对文件名的匹配,"%"表示长度任意的非空字符串。例如:"%.c"表示以".c"结尾的文件名(文件名的长度至少为3),而"s.%.c"则表示以"s."开头,".c"结尾的文件名(文件名的长度至少为 5)
2、模式规则示例
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
其中,"$@"表示所有的目标的挨个值,"$<"表示了所有依赖目标的挨个值。这些奇怪的变
量我们叫"自动化变量",后面会详细讲述
3、自动化变量
$@
表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。
$%
仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。如果目标不是函数库文件(Unix下是
[.a],Windows下是[.lib]),那么,其值为空。
$<
依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
$?
所有比目标新的依赖目标的集合。以空格分隔。
$^
所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
$+
这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。
$*
这个变量表示目标模式中"%"及其之前的部分。如果目标是"dir/a.foo.b",并且目标的模式是"a.%.b",那么,"$*"的值就是"dir /a.foo"。这个变量对于构造有关联的文件名是比
较有较。如果目标中没有模式的定义,那么"$*"也就不能被推导出,但是,如果目标文件的后缀是 make所识别的,那么"$*"就是除了后缀的那一部分。例如:如果目标是"foo.c"
,因为".c"是make所能识别的后缀名,所以,"$*"的值就是"foo"。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用"$*",除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么"$*"就是空值。
当你希望只对更新过的依赖文件进行操作时,"$?"在显式规则中很有用,
$(@D)
表示"$@"的目录部分(不以斜杠作为结尾),如果"$@"值是"dir/foo.o",那么"$(@D)"就是"dir",而如果"$@"中没有包含斜杠的话,其值就是"."(当前目录)。
$(@F)
表示"$@"的文件部分,如果"$@"值是"dir/foo.o",那么"$(@F)"就是"foo.o","$(@F)"相当于函数"$(notdir $@)"。
"$(*D)"
"$(*F)"
和上面所述的同理,也是取文件的目录部分和文件部分。对于上面的那个例子,"$(*D)"返回"dir",而"$(*F)"返回"foo"
"$(%D)"
"$(%F)"
分别表示了函数包文件成员的目录部分和文件部分。这对于形同"archive(member)"形式的目标中的"member"中包含了不同的目录很有用。
"$(<D)"
"$(<F)"
分别表示依赖文件的目录部分和文件部分。
"$(^D)"
"$(^F)"
分别表示所有依赖文件的目录部分和文件部分。(无相同的)
"$(+D)"
"$(+F)"
分别表示所有依赖文件的目录部分和文件部分。(可以有相同的)
"$(?D)"
"$(?F)"
分别表示被更新的依赖文件的目录部分和文件部分。
最后想提醒一下的是,对于"$<",为了避免产生不必要的麻烦,我们最好给$后面的那个特定字符都加上圆括号,比如,"$(< )"就要比"$<"要好一些。
还得要注意的是,这些变量只使用在规则的命令中,而且一般都是"显式规则"和"静态模式规则"(参见前面"书写规则"一章)。其在隐含规则中并没有意义。
4、模式的匹配
一般来说,一个目标的模式有一个有前缀或是后缀的"%",或是没有前后缀,直接就是一个"%"。因为"%"代表一个或多个字符,所以在定义好了的模式中,我们把"%"所匹配的内容叫做"茎",例如"%.c"所匹配的文件"test.c"中"test"就是"茎"。因为在目标和依赖目标中同时有"%"时,依赖目标的"茎"会传给目标,当做目标中的"茎"。
当一个模式匹配包含有斜杠(实际也不经常包含)的文件时,那么在进行模式匹配时,目录部分会首先被移开,然后进行匹配,成功后,再把目录加回去。在进行"茎"的传递时,我们需要知道这个步骤。例如有一个模式"e%t",文件"src/eat" 匹配于该模式,于是"src/a"就是其"茎",如果这个模式定义在依赖目标中,而被依赖于这个模式的目标中又有个模式"c%r",那么,目标就是"src/car"。("茎"被传递)
5、重载内建隐含规则
你可以重载内建的隐含规则(或是定义一个全新的),例如你可以重新构造和内建隐含规则不同的命令,如:
%.o : %.c
$(CC) -c $(CPPFLAGS) $(CFLAGS) -D$(date)
你可以取消内建的隐含规则,只要不在后面写命令就行。如:
%.o : %.s
同样,你也可以重新定义一个全新的隐含规则,其在隐含规则中的位置取决于你在哪里写下这个规则。朝前的位置就靠前。
六、老式风格的"后缀规则"
后缀规则是一个比较老式的定义隐含规则的方法。后缀规则会被模式规则逐步地取代。因为模式规则更强更清晰。为了和老版本的Makefile兼容,GNU make同样兼容于这些东西。后缀规则有两种方式:"双后缀"和"单后缀"。
双后缀规则定义了一对后缀:目标文件的后缀和依赖目标(源文件)的后缀。如".c.o"相当于"%o : %c"。单后缀规则只定义一个后缀,也就是源文件的后缀。如".c"相当于"% : %.c"。
后缀规则中所定义的后缀应该是make所认识的,如果一个后缀是make所认识的,那么这个规则就是单后缀规则,而如果两个连在一起的后缀都被make所认识,那就是双后缀规则。例如:".c"和".o"都是make所知道。因而,如果你定义了一个规则是".c.o"那么其就是双后缀规则,意义就是".c" 是源文件的后缀,".o"是目标文件的后缀。
七、隐含规则搜索算法
比如我们有一个目标叫 T。下面是搜索目标T的规则的算法。请注意,在下面,我们没有提到后缀规则,原因是,所有的后缀规则在Makefile被载入内存时,会被转换成模式规则。如果目标是"archive(member)"的函数库文件模式,那么这个算法会被运行两次,第一次是找目标T,如果没有找到的话,那么进入第二次,第二次会把"member"当作T来搜索。
1、把T的目录部分分离出来。叫D,而剩余部分叫N。(如:如果T是"src/foo.o",那么,D就是"src/",N就是"foo.o")
2、创建所有匹配于T或是N的模式规则列表。
3、如果在模式规则列表中有匹配所有文件的模式,如"%",那么从列表中移除其它的模式。
4、移除列表中没有命令的规则。
5、对于第一个在列表中的模式规则:
1)推导其"茎"S,S应该是T或是N匹配于模式中"%"非空的部分。
2)计算依赖文件。把依赖文件中的"%"都替换成"茎"S。如果目标模式中没有包含斜框字符,而把D加在第一个依赖文件的开头。
3)测试是否所有的依赖文件都存在或是理当存在。(如果有一个文件被定义成另外一个规则的目标文件,或者是一个显式规则的依赖文件,那么这个文件就叫"理当存在")
4)如果所有的依赖文件存在或是理当存在,或是就没有依赖文件。那么这条规则将被采用,退出该算法。
6、如果经过第5步,没有模式规则被找到,那么就做更进一步的搜索。对于存在于列表中的第一个模式规则:
1)如果规则是终止规则,那就忽略它,继续下一条模式规则。
2)计算依赖文件。(同第5步)
3)测试所有的依赖文件是否存在或是理当存在。
4)对于不存在的依赖文件,递归调用这个算法查找他是否可以被隐含规则找到。
5)如果所有的依赖文件存在或是理当存在,或是就根本没有依赖文件。那么这条规则被采用,退出该算法。
7、如果没有隐含规则可以使用,查看".DEFAULT"规则,如果有,采用,把".DEFAULT"的命令给T使用。
一旦规则被找到,就会执行其相当的命令,而此时,我们的自动化变量的值才会生成。
使用make更新函数库文件
———————————
一、函数库文件的成员
一个函数库文件由多个文件组成。你可以以如下格式指定函数库文件及其组成:
archive(member)
这个不是一个命令,而一个目标和依赖的定义。一般来说,这种用法基本上就是为了"ar"命令来服务的。如:
foolib(hack.o) : hack.o
ar cr foolib hack.o
如果要指定多个member,那就以空格分开,如:
foolib(hack.o kludge.o)
二、函数库成员的隐含规则
当 make搜索一个目标的隐含规则时,一个特殊的特性是,如果这个目标是"a(m)"形式的,其会把目标变成"(m)"。于是,如果我们的成员是"%.o" 的模式定义,并且如果我们使用"make foo.a(bar.o)"的形式调用Makefile时,隐含规则会去找"bar.o"的规则,如果没有定义bar.o的规则,那么内建隐含规则生效,make会去找bar.c文件来生成bar.o,如果找得到的话,make执行的命令大致如下:
cc -c bar.c -o bar.o
ar r foo.a bar.o
rm -f bar.o
还有一个变量要注意的是"$%",这是专属函数库文件的自动化变量,有关其说明请参见"自动化变量"一节。
三、函数库文件的后缀规则
你可以使用"后缀规则"和"隐含规则"来生成函数库打包文件,如:
.c.a:
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $*.o
$(AR) r $@ $*.o
$(RM) $*.o
其等效于:
(%.o) : %.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $*.o
$(AR) r $@ $*.o
$(RM) $*.o
四、注意事项
在进行函数库打包文件生成时,请小心使用make的并行机制("-j"参数)。如果多个ar命令在同一时间运行在同一个函数库打包文件上,就很有可以损坏这个函数库文件。所以,在make未来的版本中,应该提供一种机制来避免并行操作发生在函数打包文件上。
但就目前而言,你还是应该不要尽量不要使用"-j"参数。
装载任务状态段寄存器TR
在任务内发生特权级变换时堆栈也随着自动切换,外层堆栈指针保存在内层堆栈中,而内层堆栈指针存放在当前任务的TSS中。所以,在从外层向内层变换时,要访问TSS(从内层向外层转移时不需要访问TSS,而只需内层栈中保存的栈指针)。实例在进入保护模式下的临时代码段后,通过如下两条指令装载任务状态段寄存器TR,使其指向已预置好的任务的TSS:
mov ax,SelectorTSS
ltr ax
LTR指令是专门用于装载任务状态段寄存器TR的指令。该指令的操作数是对应TSS段描述符的选择子。LTR指令从GDT中取出相应的TSS段描述符,把TSS段描述符的基地址和界限等信息装入TR的高速缓冲寄存器中。
UD2指令,产生一个中断,该中断使处理器把eflags,cs,ip等压入堆栈。ud2指令的bochs调试地址为:0x30430
在网上看到2中解决此问题的方法:
1.使用dos下的debug32工具单步跟踪pmtest2.com的运行情况。但这个方法存在问题,当跟踪到mov cr0,eax语句时freedos会错误,无法继续跟踪下去。
2.使用bochs的magic_break,方法如下:
在Bochs的配置文件里加上一句magic_break: enabled=1,然后在程序里加上一句xchg bx,bx,运行到这条指令时会断到Bochs调试器里(在Bochsrc_sample.txt里)。
网上的留言的大侠使用该方法成功了,可惜我没成功,网上回了个贴,说了一下自己的操作流程,等待回复中。
今天终于把这个方法实验成功了,前面失败的原因是没有把新编译成的pmtest.com文件拷贝到pm.img文件中。下面把整个步骤完整的列一下:
1)修改bochsrc文件。
在文件末尾增加“magic_break: enabled=1”
2)pmtest.asm文件中增加语句xchg bx, bx,选了2个地方都没有成功
org 0100h
;xchg bx, bx ;我选的位置
jmp LABEL_BEGIN
3)用nasm编译新该的asm文件,并拷贝到pm.img中
sudo mount -o loop pm.img /mnt
sudo cp pmtest.com /mnt
sudo umount /mnt
4)启动bochs
bochs -f bochsrc
5)选择6
Please choose one: [6]
让bochs运行
<bochs:1> c 回车
6)在freedos中进入b盘,运行pmtest.com
7)在bochs的调试窗口中将会停在xchg bx,bx处。提示如下:
(0) Magic breakpoint
Next at t=89456572
(0) [0x00032342] 3224:0102 (unk. ctxt): jmp .+643 (0x000325c8) ; e98302
<bochs:2>
这样我们又可以用前面的方法单步调试程序了。
3.使用bochs的vb命令和 Edit options实现
参考http://blog.csdn.net/titer1/archive/2010/04/28/5540362.aspx,上面图文并茂讲的很清楚。
vb命令需要seg:ofs格式来设置断点,那如何知道seg和ofs值呢?
在网上看到一个方法:在你想下断点的位置前增加语句jmp $,当pmtest在dos下运行起来后回到bochs调试窗口,按下ctrl+c,bochs将会停在jmp $的位置,并显示出seg:ofs。我们就可以用这个值和vb来下断点了。
费力的折腾了一周,才过了这关。希望作者再版时把这部分内容加上,好让大家能集中精力学习操作系统。
bochs调试FAQ:
一 基本调试命令
1. Q:如何从引导扇区开始设置断点?
A: BIOS被载入内存中运行,其引导扇区的地址一般都是在0x7c00,因为在实模式下,逻辑地址和物理地址是一一对应的, 所以我们可以在启动bochs调试的命令行下输入
pb 0x7c00 或者 vb 0:0x7c00
就着输入
c
bochs就会在0x7c00处停下
2. Q: 我想在单步执行(s)后看到寄存器的变化情况, 该怎么设置?
A: 输入
trace-reg on
后, 再执行单步调试的时候都会显示寄存器的当前状态了.
3. Q: 在单步调试的时候,遇到一个调用子程序的指令,输入s会把调试跳进子程序中,如何只执行子程序而不要陷进去调试?
A: 在调试子程序的指令处,不用s改为n或p
4. Q: 查看当前堆栈的命令?
A: print-stack
Table 1. 部分Bochs调试指令
行为
指令
举例
在某物理地址设置断点
b addr
b 0x30400显示当前所有断点信息
info break继续执行,直到遇上断点
c单步执行
s单步执行(遇到函数则跳过)
n查看寄存器信息
info cpu
r
fp
sreg
creg
info cpu
r
fp
sreg
creg
查看堆栈
print-stack查看内存物理地址内容
xp /nuf addr
xp /40bx 0x9013e
查看线性地址内容
x /nuf addr
x /40bx 0x13e
反汇编一段内存
u start end
u 0x30400 0x3040D
反汇编执行的每一条指令
trace-on每执行一条指令就打印CPU信息
trace-reg
trace-reg on
其中"xp /40bx 0x9013e"这样的格式可能显得有点复杂,读者可以用"help x"这一指令在Bochs中亲自看一下它代表的意义
二执行控制
c|cont 向下执行,相当于WinDBG的“g”。
s|step|stepi [count] 单步执行,相当于WinDBG的“t”,count 默认为 1。
p|n|next 单步执行,类似于WinDBG的“p”。
q|quit|exit 退出调试,同时关闭虚拟机。
Ctrl-C 结束执行状态,返回调试器提示符。
Ctrl-D if at empty line on command line, exit
(至少在Windows版本中我没有发现Ctrl-D有什么功能)
三 执行断点
vb|vbreak [seg:off] 在虚拟地址上下断点。
lb|lbreak [addr] 在线性地址上下断点,相当于WinDBG的“bp”。
pb|pbreak|b|break [addr] 在物理地址上下断点。(为了兼容GDB的语法,地址前可以加上一个“*”)。
blist 显示断点状态,相当于WinDBG的“bl”。
bpd|bpe [num] 禁用/启用断点,WinDBG的“be”和“bd”。num是断点号,可以用blist命令查询。
d|del|delete [num] 删除断点,相当于WinDBG的“bc”。mum是断点号,可
以用blist命令查询。
四 读写断点
watch read [addr] 设置读断点。
watch write [addr] 设置写断点。
unwatch read [addr] 清除读断点。
unwatch write [addr] 清除写断点。
watch 显示当前所有读写断点。
unwatch 清除当前所有读写断点。
watch stop|continue 开关选项,设置遇到读写断点时中断下来还是显示出来但是继续运行。
五 内存操作
x /nuf [addr] 显示线性地址的内容
xp /nuf [addr] 显示物理地址的内容
n 显示的单元数
u 每个显示单元的大小,u可以是下列之一:
b BYTE
h WORD
w DWORD
g DWORD64
注意: 这种命名法是按照GDB习惯的,而并不是按照inter的规范。
f 显示格式,f可以是下列之一:
x 按照十六进制显示
d 十进制显示
u 按照无符号十进制显示
o 按照八进制显示
t 按照二进制显示
c 按照字符显示
n、f、u是可选参数,如果不指定,则u默认是w,f默认是x。如果前面使用过x或
者xp命令,会按照上一次的x或者xp命令所使用的值。n默认为1。addr 也是一个
可选参数,如果不指定,addr是0,如过前面使用过x或者xp命令,指定了n=i,
则再次执行时n默认为i+1。
setpmem [addr] [size] [val] 设置物理内存某地址的内容。
需要注意的是,每次最多只能设置一个DWORD:
这样是可以的:
<bochs:1> setpmem 0x00000000 0x4 0x11223344
<bochs:2> x /4 0x00000000
[bochs]:
0x00000000 <bogus+ 0>: 0x11223344 0x00000000 0x00000000 0x00000000
这样也可以:
<bochs:1> setpmem 0x00000000 0x2 0x11223344
<bochs:2> x /4 0x00000000
[bochs]:
0x00000000 <bogus+ 0>: 0x00003344 0x00000000 0x00000000 0x00000000
或者:
<bochs:1> setpmem 0x00000000 0x1 0x20
<bochs:2> x /4 0x00000000
[bochs]:
0x00000000 <bogus+ 0>: 0x00000020 0x00000000 0x00000000 0x00000000
下面的做法都会导致出错:
<bochs:1> setpmem 0x00000000 0x3 0x112233
Error: setpmem: bad length value = 3
<bochs:2> setpmem 0x00000000 0x8 0x11223344
Error: setpmem: bad length value = 8
crc [start] [end] 显示物理地址start到end之间数据的CRC。
六 寄存器操作
set $reg = val 设置寄存器的值。现在版本可以设置的寄存器包括:
eax ecx edx ebx esp ebp esi edi
暂时不能设置:
eflags cs ss ds es fs gs
r|reg|registers reg = val 同上。
dump_cpu 显示完整的CPU信息。
set_cpu 设置CPU状态,这里可以设置dump_cpu所能显示出来的所有CPU状态。
七 反汇编命令
u|disas|disassemble [/num] [start] [end]
反汇编物理地址start到end 之间的代码,如
果不指定参数则反汇编当前EIP指向的代码。
num是可选参数,指定处理的代码量。
set $disassemble_size = 0|16|32 $disassemble_size变量指定反汇编使用的段大小。
set $auto_disassemble = 0|1 $auto_disassemble决定每次执行中断下来的时候(例如遇到断点、Ctrl-C等)是否反汇编当前指令。
八 其他命令
trace-on|trace-off Tracing开关打开后,每执行一条指令都会将反汇编的结果
显示出来。
ptime 显示Bochs自本次运行以来执行的指令条数。
sb [val] 再执行val条指令就中断。val是64-bit整数,以L结尾,形如“1000L”
sba [val] 执行到Bochs自本次运行以来的第val条指令就中断。val是64-bit整数,以L结尾,形如“1000L”
modebp 设置切换到v86模式时中断。
record ["filename"] 将输入的调试指令记录到文件中。文件名必须包含引号。
playback ["filename"] 回放record的记录文件。文件名必须包含引号。
print-stack [num] 显示堆栈,num默认为16,表示打印的条数。
?|calc 和WinDBG的“?”命令类似,计算表达式的值。
load-symbols [global] filename [offset]
载入符号文件。如果设定了“global”关键字,则符号针对所有上下文都有效。offset会默认加到所有的symbol地址上。symbol文件的格式为:"%x %s"。
九 info命令
info program 显示程序执行的情况。
info registers|reg|r 显示寄存器的信息。
info pb|pbreak|b|break 相当于blist
info dirty 显示脏页的页地址。
info cpu 显示所有CPU寄存器的值。
info fpu 显示所有FPU寄存器的值。
info idt 显示IDT。
info gdt [num] 显示GDT。
info ldt 显示LDT。
info tss 显示TSS。
info pic 显示PIC。
info ivt [num] [num] 显示IVT。
info flags 显示状态寄存器。
info cr 显示CR系列寄存器。
info symbols 显示symbol信息。
info ne2k|ne2000 显示虚拟的ne2k网卡信息。
这是因为 .com 载入内存后的起始偏址就是100h. 前面的100h字节是该程序的PSP 部分. 所以, 为了程序中对地址引用的正确, 必需加上org 100h语句。
-------------------------------------------------------------------------------------------------------------------------------------------------
FFFF0h与07C00h,这两个都是机器启动后默认访问的内存地址。曾经让我一度很晕,搞不清他们之间的关系。现在终于搞明白了,
首先要知道bios是用来初始化硬件的最底层的软件(然后才是操作系统),因此计算机启动后必须最先被执行。另外我们都知道CPU只能执行内存中的内容的,而一般内存中的数据是易失性的,断电之后内容就会消失。工程师的解决方法是:将存放bios的rom芯片与内存芯片统一编址(不明白的话去看看微机原理与接口的书就明白了)。这样就可以把存放bios的ROM芯片看作是数据永远不会消失不允许被更改的内存了。
开机启动后默认的CS=FFFFh IP=0000h。这个地址就是bios的地址。这段内存空间很小,所以不能够容下操作系统等大型程序。
相对bios而言操作系统的功能更强大,更新也更快但是也需要更多的空间,通常放在硬盘中。但是如果没有bios的话,那么将会连硬盘都不能使用,又如何启动存放在硬盘中的操作系统呢?正是由于此,机器启动后自动执行bios使其它完成硬件初始化(这样包括硬盘在内的cpu以外的其他硬件设备就可以工作了)。bios完成硬件初始化的任务后,就要把权力移交给操作系统。
工程师进行了强制性的规定:到内存中的07c00h 处寻找系统的引导程序,即CS=0000h IP=7c00h。也就是说任何系统,他的引导程序都必须安排在07c00h开始的地方,否则就不能被正确的引导。当引导程序完成后我们就进入了Linux Windows等系统了。
后面为了调试方便,还可以吧org 07c00h改为0100h,生成 com文件就能直接在dos下运行。cx表示的是字符串的个数,10进制的,可以自行修改,boot sector的结束地址也是固定的,为0xAA55,呵呵,好玩
书中的代码如果要在DOS下运行,需要把org 07C00h改为org 0100h,这样做的目的是因为在DOS下,装载程序会事先创建一个称为程序段前缀(PSP)256字节的数据区,用来和被装载的程序通信,具体的细节请查验相关资料。而0100h刚好是256个字节的处,这也是代码段开始执行处。
说完程序的加载运行,让我们看看BIOS的10号中断处理子程序的显示功能。这里10号中断有很多子程序,书中用到了13子程序,此子程序就是显示字符串的,其中的参数如下:ES:BP=串地址;CX=串长度;DH=起始行;DL=起始列;BH=页号;BL=属性;AH=子程序编号,这里为13;这里BL是这样子定义的:闪烁 背景红 背景绿 背景蓝 高亮 前景红 前景绿 前景蓝(顺序为高字节到低字节)。这样BL=0Ch,二进制为:1100,由其定义可知为黑底高亮红字。
SECTION是一种组织代码和存储的方式
NASM支持标准的.data, .text和.bss,编译后的程序文件中的内存地址顺序是.text, .data,用户自定义section。
NASM支持用户自定义section
同名的section,编译后会放在同一块连续的内存上
[SECTION .data]
var1 db 0x01
[SECTION .text]
MOV AX, var1
[SECTION .data]
var2 db 0x02
编译后,内存为0xB8040000 0102,其中0xB804是MOV AX,0x04的机器码,0x04是标号var1汇编后的偏移地址。因为汇编后,var1对应的存储区在.data段,被挪到了内存的尾部,因此偏移不是0x00,而变成了0x04。
对用户自定义section,按照出现的先后顺序存储,同名的section存储在一起。
每个SECTION默认都是按4字节对齐的:
[SECTION .s1]
var1 db 0x1
[SECTION .s2]
var2 db 0x3
[SECTION .s1]
var3 db 0x2
编译后产生的内存:0x01020000 0x03
可以看到SECTION .s1被扩展为4个字节,后面两个字节填0,然后是SECTION .s2
SECTION的对齐方式可以用ALIGN来调整
$$指向当前section相对于段基址的偏移地址,$指向当前行相对于段基址的偏移地址。
显示服务 (Video Service:int 10h)
00H:设置显示器模式
01H:设置光标形状
02H:设置光标位置
03H:读取光标信息
04H:读取光笔位置
05H:设置显示页
06H、07H:初始化或滚屏
08H:读光标处的字符及其 属性
09H:在光标处按指定属性显示字符
0AH:在当前光标处显示字符
0BH:设置调色板、背景色或边框
0CH: 写图形象素
0DH:读图形象素
0EH:在Teletype模式下显示字符
0FH:读取显示器模式
10H:颜色
11H: 字体
12H:显示器的配置
13H:在Teletype模式下显示字符串
1AH:读取/设置显示组合编码
1BH: 读取功能/状态信息
1CH:保存/恢复显示器状态
以下是每个功能号的详细说明:
1、功能00H
功能描述:设置显示器模式
入口参数:AH=00H
AL= 显示器模式,见下表所示
出口参数:无
可用的显示模式如下所列:
00H:40×25 16色 文本
01H:40×25 16 色 文本
02H:80×25 16色 文本
03H: 80×25 16色 文本
04H:320×200 4色
05H:320×200 4 色
06H:640×200 2色
07H:80×25 2色 文本
08H:160×200 16色
09H:320×200 16 色
0AH:640×200 4色
0BH:保留
0CH:保留
0DH:320×200 16色
0EH:640×200 16 色
0FH:640×350 2(单色)
10H:640×350 4色
10H:640×350 16色
11H:640×480 2 色
12H:640×480 16色
13H:640×480 256色
对于超级VGA显示卡,我们可用AX=4F02H和下列BX 的值来设置其显示模式。
BX显示模式属性
100H:640×400 256色
101H:640×480 256色
102H:800×600 16 色
103H:800×600 256色
104H:1024×768 16色
105H:1024×768 256色
106H:1280×1024 16 色
107H:1280×1024 256色
108H:80×60 文本模式
109H:132×25 文本模式
10AH:132×43 文 本模式
10BH:132×50 文本模式
10CH:132×60 文本模式
2、功能01H
功能描述:设置 光标形状
入口参数:AH=01H
CH低四位=光标的起始行
CL低四位=光标的终止行
出口参数:无
3、 功能02H
功能描述:用文本坐标下设置光标位置
入口参数:AH=02H
BH=显示页码
DH=行(Y坐标)
DL= 列(X坐标)
出口参数:无
4、功能03H
功能描述:在文本坐标下,读取光标各种信息
入口参 数:AH=03H
BH=显示页码
出口参数:CH=光标的起始行
CL=光标的终止行
DH=行(Y坐标)
DL=列 (X坐标)
5、功能04H
功能描述:获取当前状态和光笔位置
入口参数:AH=04H
出口参 数:AH=00h——光笔未按下/未触发,01h——光笔已按下/已触发
BX=象素列(图形X坐标)
CH=象素行(图形Y坐标,显示模 式:04H~06H)
CX=象素行(图形Y坐标,显示模式:0DH~10H)
DH=字符行(文本Y坐标)
DL=字符列(文本X坐 标)
6、功能05H
功能描述:设置显示页,即选择活动的显示页
入口参数:AH=05H
AL=显示页
对 于CGA、EGA、MCGA和VGA,其显示页如下表所列: 模式页数显示器类型
00H、01H0~7CGA、EGA、MCGA、VGA
02H、 03H0~3CGA
02H、03H0~7EGA、MCGA、VGA
07H0~7EGA、VGA
0DH0~7EGA、VGA
0EH0~3EGA、 VGA
0FH0~1EGA、VGA
10H0~1EGA、VGA
对于PCjr:
AL=80H——读取CRT/CPU页寄存 器
81H——设置CPU页寄存器
82H——设置CRT页寄存器
83H——设置CRT/CPU页寄存器
BH=CRT页(子 功能号82H和83H)
BL=CPU页(子功能号81H和83H)
出口参数:对于前者,无出口参数,但对PCjr在子功能80H~83H 调用下,有:BH=CRT页寄存器,BL=CPU页寄存器
7、功能06H和07H
功能描述:初始化屏幕或滚屏
入口 参数:AH=06H——向上滚屏,07H——向下滚屏
AL=滚动行数(0——清窗口)
BH=空白区域的缺省属性
(CH、CL)= 窗口的左上角位置(Y坐标,X坐标)
(DH、DL)=窗口的右下角位置(Y坐标,X坐标)
出口参数:无
8、功能 08H
功能描述:读光标处的字符及其属性
入口参数:AH=08H
BH=显示页码
出口参数:AH=属性
AL=字 符
9、功能09H
功能描述:在当前光标处按指定属性显示字符
入口参数:AH=09H
AL=字符
BH= 显示页码
BL=属性(文本模式)或颜色(图形模式)
CX=重复输出字符的次数
出口参数:无
10、功能 0AH
功能描述:在当前光标处按原有属性显示字符
入口参数:AH=0AH
AL=字符
BH=显示页码
BL=颜色 (图形模式,仅适用于PCjr)
CX=重复输出字符的次数
出口参数:无
11、功能0BH
功能描述:设置调 色板、背景色或边框
入口参数:AH=0BH
设置颜色:BH=00H,01=颜色
选择调色板:BH=01H,BL=调色板 (320×200、4种颜色的图形模式)
出口参数:无
12、功能0CH
功能描述:写图形象素
入口参 数:AH=0CH
AL=象素值
BH=页码
(CX、DX)=图形坐标列(X)、行(Y)
出口参数:无
13、功能0DH
功能描述:读图形象素
入口参数:AH=0DH
BH=页码
(CX、DX)=图形坐标列(X)、行(Y)
出 口参数:AL=象素值
14、功能0EH
功能描述:在Teletype模式下显示字符
入口参数:AH=0EH
AL= 字符
BH=页码
BL=前景色(图形模式)
出口参数:无
15、功能0FH
功能描述:读取显示器模式
入 口参数:AH=0FH
出口参数:AH=屏幕字符的列数
AL=显示模式(参见功能00H中的说明)
BH=页码
16、功能10H
功能描述:颜色中断。
其子功能说明如下:
00H — 设置调色板寄存器
01H — 设置边框颜色
02H — 设 置调色板和边框
03H — 触发闪烁/亮显位
07H — 读取调色板寄存器
08H — 读取边框颜色
09H — 读取调 色板和边框
10H — 设置颜色寄存器
12H — 设置颜色寄存器块
13H — 设置颜色页状态
15H — 读取颜色寄 存器
17H — 读取颜色寄存器块
1AH — 读取颜色页状态
1BH — 设置灰度值
17、功能11H
功能描述:字体中断。
其子功能说明如下:
00H装入用户字体和可编程控制器
10H装入用户字体和可编程控制器
01H装入 8×14 ROM字体和可编程控制器
11H装入8×14 ROM字体和可编程控制器
02H装入8×8 ROM字体和可编程控制器
12H 装入8×8 ROM字体和可编程控制器
03H设置块指示器
04H装入8×16 ROM字体和可编程控制器
14H装入 8×16 ROM字体和可编程控制器
20H设置INT 1Fh字体指针
21H为用户字体设置INT 43h
22H为 8×14 ROM字体设置INT 43H
23H为8×8 ROM字体设置INT 43H
24H为8×16 ROM字体设置INT 43H
30H 读取字体信息
18、功能12H
功能描述:显示器的配置中断。
其子功能说明如下:
10H — 读取配置信息
20H — 选 择屏幕打印
30H — 设置扫描行
31H — 允许/禁止装入缺省调色板
32H — 允许/禁止显示
33H — 允许/ 禁止灰度求和
34H — 允许/禁止光标模拟
35H — 切换活动显示
36H — 允许/禁止屏幕刷新
19、功能 13H
功能描述:在Teletype模式下显示字符串
入口参数:AH=13H
BH=页码
BL=属性(若AL=00H或 01H)
CX=显示字符串长度
(DH、DL)=坐标(行、列)
ES:BP=显示字符串的地址 AL=显示输出方式
0—— 字符串中只含显示字符,其显示属性在BL中。显示后,光标位置不变
1——字符串中只含显示字符,其显示属性在BL中。显示后,光标位置改变
2 ——字符串中含显示字符和显示属性。显示后,光标位置不变
3——字符串中含显示字符和显示属性。显示后,光标位置改变
出口参数:无
20、 功能1AH
功能描述:读取/设置显示组合编码,仅PS/2有效,在此从略
21、功能1BH
功能描述:读取功能/ 状态信息,仅PS/2有效,在此从略
22、功能1CH
功能描述:保存/恢复显示器状态,仅PS/2有效,在此从略
宏语法格式如下:
%macro 宏名称 参数个数
宏的内容
%endmacro
宏的第一个参数用%1表示,第二个参数用%2表示,依次类推。
最近看orange’s 一个操作系统的实现,发现nasm中的宏汇编不是很清楚,在此详细记录一下
%macro Descriptor 3
dw %1 & 0FFFFh ; 段基址1
dw %2 & 0FFFFh ; 段界限1
db (%1>>16) & 0FFh ; 段基址2
dw ((%2>>8) & 0F00h) | (%3 & 0F0FFh) ; 属性1+段界限2+属 性2
db (%1>>24) & 0FFh ; 段基址3
%endmacro;
共 8 字节 在段[SECTION.gdt]中并列有3个Descriptor,看上去是个结构数
上面的是宏的详细情况,下面是调用宏
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
其中Descriptor 0,0,0表示传入的三个参数,这三个参数在宏定义中就是以%1,%2,%3表示,所以将%1用传入的参数替换掉即可
替换后的情况即:
dw 0 & 0FFFFh ; 段基址1
dw 0 & 0FFFFh ; 段界限1
db (0>>16) & 0FFh ; 段基址2
dw ((0>>8) & 0F00h) | (0 & 0F0FFh) ; 属性1+段界限2+属 性2
db (0>>24) & 0FFh ; 段基址3
%endmacro;
共 8 字节 在段[SECTION.gdt]中并列有3个Descriptor,看上去是个结构数
就是这么简单
PROCESS* p_proc=proc_table;
p_proc--->ldt_sel=SELECTOR_LDT_FIRST;
//设置进程表中进程的ldt_sel,ldt_sel被赋值SELECTOR_LDT_FIRST,这个宏的定义在代码6-1-中
memcpy(&p_proc->ldts[0], &gdt[SELECTOR_KERNEL_CS >> 3], sizeof(DESCRIPTOR)); //将SELECTOR_KERNEL_CS所指的描述符拷贝到进程PCB的ldts[0]处
p_proc->ldts[0].attr1 = DA_C | PRIVILEGE_TASK << 5; // change the DPL
memcpy(&p_proc->ldts[1], &gdt[SELECTOR_KERNEL_DS >> 3], sizeof(DESCRIPTOR)); //将SELECTOR_KERNEL_DS所指的描述符拷贝到进程PCB的ldts[1]处
p_proc->ldts[1].attr1 = DA_DRW | PRIVILEGE_TASK << 5; // change the DPL
//LDT中共有两个描述符,分别被初始化为内核代码段和内核数据段,只是改变了一下DPL,以让其运行在低特权级下(2)
p_proc->regs.cs = ((8 * 0) & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK; //8*0和8*1是选择子,而且是进程PCB中的LDT描述符的选择子。
//cs指向LDT中的第一个描述符 (3) 通过cs中的这个描述符跳转到内核代码段
//LDT选择子是从0开始的。一个描述符相隔8个字节,所以要乘以8
p_proc->regs.ds = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK; //根据选择子的属性确定是全局选择子还是LDT选择子。
p_proc->regs.es = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK; //本例会根据cs中的选择子自动到进程PCB中读取LDT描述符
p_proc->regs.fs = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK; //本例比较特殊的地方是进程LDT描述符在进程PCB中,没有固定的LDT段
p_proc->regs.ss = ((8 * 1) & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_TASK;
//ds,es,fs,ss指向LDT中的第二个描述符
p_proc->regs.gs = (SELECTOR_KERNEL_GS & SA_RPL_MASK) | RPL_TASK;
//gs指向显存,只是其RPL发生改变
p_proc->regs.eip = (u32)TestA;
//eip指向TestA,这表明进程将从TestA的入口地址开始运行
p_proc->regs.esp = (u32) task_stack + STACK_SIZE_TOTAL;
//esp指向单独的堆栈,堆栈大小为STACK_SIZE_TOTAL
p_proc->regs.eflags = 0x1202; // IF=1, IOPL=1, bit 2 is always 1.
//eflags=0x1202,恰巧设置了IF位,并把IOPL设为1,这样,进程就可以使用I/O指令,并且中断会在iretd执行时,被打开。
//代码中使用到的宏,基本在protect.h中
p_proc_ready = proc_table;
这里要记得把LDT跟GDT是联系在一起的,别忘了填充GDT中进程的LDT的描述符。protect.c
init_descriptor(
&gdt[INDEX_LDT_FIRST], //设置填充地址位置(GDT中的LDT描述符 )
vir2phys(seg2phys(SELECTOR_KERNEL_DS), proc_table[0].ldts), //LDT基址,这个GDT中的LDT基址指向proc_table[0].ldts (1)
LDT_SIZE * sizeof(DESCRIPTOR) - 1, //LDT大小
DA_LDT); //LDT属性
seg2phys(SELECTOR_KERNEL_DS)把段地址转换为物理地址,把DS右移几位。
vir2phys为宏定义,在protect.h中
#define vir2phys(seg_base, vir) (u32)(((u32)seg_base) + (u32)(vir))
定点运算和定点数制转换
;范例1
LSDAA: ADC R16,R16 ;十进制数(在R16中)左移调整子程序
ADDAA: IN R6,SREG ;bcd码相加调整子程序,先保存相加后的
LDI R17,$66 ;状态the old status
ADD R16,R17 ;再将和预加立即数$66
IN R17,SREG ;输入相加后新状态(the new status)
OR R6,R17 ;新旧状态相或
SBRS R6,0 ;相或后进位置位则跳行
SUBI R16,$60 ;否则减去$60(十位bcd不满足调整条件)
SBRS R6,5 ;半进位置位则跳行
SUBI R16,6 ;否则减去$06(个位bcd不满足调整条件)
ROR R6 ;向高位字节BCD返还进位位!
RET
;范例2
SUDAA: BRCC SBD1 ;bcd码减法调整子程序,差在R16中
BRHC SBD3
SUBI R16,$66 ;进位半进位都置位,将差减去立即数$66
SEC ;并恢复借位C
RET ;ret. with seC
SBD1: BRHC SBD2 ;进位半进位都清位,返回
SUBI R16,6 ;进位清除而半进位置位,将差减去6
SBD2: RET ;ret. with clC
SBD3: SUBI R16,$60 ;进位置位而半进位清除,将差减去$60
SEC ;并恢复借位C
RET ;ret. with seC
;范例3
RSDAA: SBRC R16,7 ;bcd码(在R16中)右移调整子程序
SUBI R16,$30 ;十位BCD最高位为1(代表8),将其变为5(否则跳行)
SBRC R16,3
SUBI R16,3 ;个位BCD最高位为1(代表8),将其变为5(否则跳行)
RET
;范例4
ADBCD4: MOV R16,R15 ;4字节压缩bcd码相加子程序
ADD R16,R11 ;R12,R13,R14,R15内为被加数,R8,R9,R10,R11内为加数
RCALL ADDAA ;相加后调整
MOV R15,R16 ;并返还调整后结果
MOV R16,R14
ADC R16,R10
RCALL ADDAA
MOV R14,R16
MOV R16,R13
ADC R16,R9
RCALL ADDAA
MOV R13,R16
MOV R16,R12
ADC R16,R8
RCALL ADDAA
MOV R12,R16
RET
;范例5
ADBCD: LDI R16,4 ;多字节压缩bcd码相加子程序
MOV R7,R16 ;(r7):字节数
CLC
ADLOP: LD R16,-X ;X-1指向被加数;
LD R6,-Y ;Y-1指向加数
ADC R16,R6
RCALL ADDAA ;相加后调整
ST X,R16 ;返还调整后结果
DEC R7
BRNE ADLOP
RET
;范例6
SUBCD4: MOV R16,R15 ;4字节压缩bcd码减法子程序
SUB R16,R11 ;R12,R13,R14,R15内为被减数,R8,R9,R10,R11内为减数
RCALL SUDAA ;相减后调整
MOV R15,R16 ;并返还调整后结果
MOV R16,R14
SBC R16,R10
RCALL SUDAA
MOV R14,r16
MOV R16,R13
SBC R16,R9
RCALL SUDAA
MOV R13,R16
MOV R16,R12
SBC R16,R8
RCALL SUDAA
MOV R12,R16
RET
;范例7
SUBCD: LDI R16,4 ;多字节压缩bcd码相减子程序
MOV R7,R16 ;(r7):压缩bcd码字节数
CLC
SUBLP: LD R16,-X ;X-1指向被减数
LD R6,-Y ;Y-1指向减数
SBC R16,R6
RCALL SUDAA ;相减后调整
ST X,R16 ;返还调整后结果
DEC R7
BRNE SUBLP
RET
;范例8 ;16位被乘数*16位乘数-->32位积
MUL16: LDI R16,17 ;(r10r11)*(r14r15)-->r12r13r14r15
ClR R12
ClR R13 ;积的高位字预清除
CLC ;第1次只右移,不相加
MLOOP: BRCC MUL1 ;
ADD R13,R11 ;乘数右移移出位为1,将被乘数加入部分积
ADC R12,R10
MUL1: ROR R12
ROR R13
ROR R14
ROR R15 ;部分积连同乘数整体右移1位
DEC R16
BRNE MLOOP ;17次右移后结束
RET
;范例9 ;16位整数被乘数*16位小数乘数-->16位整数积,精确到0.5
MUL165: RCALL MUL16 ;先得到32位积
SBRS R14,7 ;积小数部分最高位为1,将整数部分加1
RET ;否则返回
LDI R17,255
SUB R13,R17
SBC R12,R17 ;以减去-1($FFFF)替代加1
RET
;范例10 ;32位被除数/16位除数-->16位商,精确到1
DIV16: LDI R16,16 ;(r12r13r14r15)/(r10r11)-->r14r15
DLOOP: LSL R15
ROL R14
ROL R13
ROL R12 ;被除数左移1位
BRCS DI1
SUB R13,R11
SBC R12,R10 ;移出位为0,被除数高位字减去除数试商
BRCC DI2 ;够减,本位商为1
ADD R13,R11
ADC R12,R10 ;否则恢复被除数
RJMP DI3 ;本位商0
DI1: SUB R13,R11
SBC R12,R10 ;移出位为1,被除数高位字减去除数
DI2: INC R15 ;本位商1
DI3: DEC R16
BRNE DLOOP
RET
;范例11 ;32位被除数/16位除数-->16位商,精确到0.5
;可能产生溢出!例$7FFFC000/$8000=$FFFF.8->$10000!
DIV165: RCALL DIV16 ;(r12r13r14r15)/(r10r11)-->r14r15
LSL R13
ROL R12 ;余数乘2
BRCS D165 ;有进位,转5入
SUB R13,R11
SBC R12,R10 ;否则,余数乘2减去除数
BRCS D164 ;不够减,转4舍
D165: CLR R13 ;否则将商增1
SEC
ADC R15,R13
ADC R14,R13
ADC R13,R13 ;若有溢出,溢出位在R13中
RET
D164: CLR R13
RET
;范例12 ;32位整数/16位整数->16整数+16位小数->4字节浮点数
;(r12r13r14r15)/(r10r11)-->r12r13r14r15
DIV16F: RCALL DIV16 ;先做整数除法
MOV R9,r15
MOV R8,r14 ;保存整数部分
CLR R15
CLR R14
RCALL DIV16 ;除得小数部分
MOV R11,R15
MOV R15,R14
MOV R13,R8
MOV R14,R9 ;整数部分在r13r14,小数部分在r15r11
LDI R17,$90 ;预设阶码$90(整数为16位)
MOV R12,R17
LDI R17,32 ;设32次右移
DIV16L: SBRC R13,7
RJMP NMLDN ;最高位为1,已完成规格化
LSL R11 ;否则继续右移R13,R14,R15,R11
ROL R15
ROL R14
ROL R13
DEC R12 ;阶码减1
DEC R17
BRNE DIV16L
CLR R12 ;右移达32次,浮点数为零,置零阶
RET
NMLDN: SBRS R11,7
RJMP DIVRT ;欲舍去部分(R11)最高位为0,转4舍
RCALL INC3 ;否则尾数部分增1
BRNE DIVRT
INC R12 ;尾数增1后变为0,改为0.5,并将阶码增1
DIVRT: LDI R17,$7F ;将尾数最高位清除,表示正数(负数不要清除)
AND R13,R17 ;规格化浮点数在R12(阶码)R13R14R15(尾数)中
RET
;范例13 ;(R16,R12,R13,R14,R15)/(R10,R11)-->R13,R14,R15
DIV24: CLR R16 ;32位整数/16位整数->24位整数,要求(R10)不为0;否则
;要求(R12)<(R11)
DIV40: LDI 17,24 ;40位整数/16位整数->24位整数 要求(R16,R12)
LXP: LSL R15 ; <(R10,R11)
ROL R14
ROL R13
ROL R12
ROL R16
BRCC LXP1
SUB R12,R11 ;右移后C=1 够减
SBC R16,R10 ;被除数减去除数
RJMP DIV0 ;本位商为1
LXP1: SUB R12,R11 ;C=0
SBC R16,R10 ;被除数减去除数试商
BRCC DIV0 ;C=0 够减,本位商1
ADD R12,R11
ADC R16,R10 ;否则恢复被除数,本位商0
RJMP DIV1
DIV0: INC R15 ;记本位商1
DIV1: DEC R17
BRNE LXP
LSL R12
ROL R16
BRCS GINC ;C=1,5入
SUB R12,R11
SBC R16,R10
BRCS RET3 ;不够减,舍掉
GINC: RCALL INC3 ;将商增1
RET3: RET
;范例14 ;定点整数(最大$FFFFFFFF)开平方子程序
INTSQR: LDI R16,17 ;SQR(R12,R13,R14,R15)-->(r15r8r9)
CLR R8 ;R8,R9存储平方根
CLR R9 ;r10,r11,r12,r13,r14,r15
CLR R10 ; r8, r9(根) r16 (counter)
CLR R11 ;r10,r11:被开平方数扩展字节
LDI R17,$40
SQR0: SUB R12,R17
SBC R11,R9
SBC R10,R8
BRCS SQR1
SEC ;试根够减,本位根1
RJMP SQR2
SQR1: ADD R12,R17
ADC R11,R9
ADC R10,R8
CLC ;否则恢复被开平方数,本位根0
SQR2: DEC R16
BRNE SQR3 ;when the No.17bit of root be getting
SQR20: ADC R9,R15 ;R15 HAVE BEEN CLEARED!
ADC R8,R15
ADC R15,R15 ;将开出之根4舍5入,使根最大可达65536(=$10000)!
RET ;for example:sqr.($ffff0001)≈$10000
SQR3: ROL R9
ROL R8 ;记本位根
LSL R15
ROL R14
ROL R13
ROL R12
ROL R11
ROL R10 ;被开平方数连同其扩展字节左移一位
LSL R15
ROL R14
ROL R13
ROL R12
ROL R11
ROL R10 ;被开平方数连同其扩展字节再次左移一位/左移2位开出1位根
BRCS SQR20 ;被开平方数左移2位后,若进位置位,则仅表明第17位根
;已被提前开出且该位根=1,将平方根增1,开平方结束。
RJMP SQR0 ;否则转试下一位根
;范例15 ;定点整数二翻十
CONV1: LDI R17,24 ;r12r13r14r15<--(r9r10r11)左移24次
MOV R7,R17 ;例:16777215<--$FFFFFF
CLR R12
CLR R13 ;68719476735<--$FFFFFFFFF
CLR R14 ;1099511627775<--$FFFFFFFFFF
CLR R15 ;十进制数存储区予清除
CV1: LSL R11
ROL R10
ROL R9 ;二进制数整体左移一位
MOV R16,R15
RCALL LSDAA
MOV R15,R16
MOV R16,R14
RCALL LSDAA
MOV R14,R16
MOV R16,R13
RCALL LSDAA
MOV R13,R16
MOV R16,R12
RCALL LSDAA ;十进制数左移并调整
MOV R12,R16
DEC R7
BRNE CV1
RET
;范例16 ;定点整数十翻二
CONV2: LDI R17,24 ;(r9r10r11)-->r13r14r15,右移24次
CLR R31 ;例:999999-->$0F423F
MOV R7,R17 ; 99999999-->$05F5E0FF
CV2: LSR R9
ROR R10
ROR R11
ROR R13
ROR R14
ROR R15 ;十进制数连同二进制数右移一位
LDI R30,12 ;数据指针
CV2L: LD R16,-Z
RCALL RSDAA ;十进制数右移调整
ST Z,R16
CPI R30,9 ;十进制数各字节调整完毕?
BRNE CV2L
DEC R7 ;右移次数(24次)完成?
BRNE CV2
RET
;范例17 ;定点小数二翻十
CONV3: LDI R17,24 ;(r13r14r15)--->r9r10r11r12右移24次
CONV31: MOV R7,R17
CLR R9
CLR R10 ;例:$0.FFFFFF-->0.99999994
CLR R11 ;$0.FFFFFFFF-->0.999999999767
CLR R12 ;$0.FFFFFFFFF->0.999999999985448
CLR R31
CV3: LSR R13
ROR R14
ROR R15
ROR R9
ROR R10
ROR R11
ROR R12 ;二进制数连同十进制数右移一位
LDI R30,9
CV3L: LD R16,Z
RCALL RSDAA ;十进制数右移调整
ST Z+,r16
CPI R30,13
BRNE CV3L ;十进制数各字节调整完毕?
DEC R7
BRNE CV3 ;右移次数(24次)完成?
RET
;范例18 ;定点小数十翻二
CONV4: LDI R17,32 ;r12r13r14r15<--r8r9r10r11<--(r12r13r14r15)
MOV R7,R17 ;左移32次
CV4: CLC ;例:$0.FFFFFFD5<--0.99999999
MOV R16,R15 ;$0.FFFFFFFF92<--0.9999999999
RCALL LSDAA
MOV R15,R16
MOV R16,R14
RCALL LSDAA
MOV R14,R16
MOV R16,R13
RCALL LSDAA
MOV R13,R16
MOV R16,R12
RCALL LSDAA
MOV R12,R16 ;定点十进制小数左移并调整
ROL R11
ROL R10
ROL R9
ROL R8 ;定点二进制小数带进位位左移一位
DEC R7
BRNE CV4
MOV R12,R8 ;最终结果转入R12--R15
MOV R13,R9
MOV R14,R10
MOV R15,R11
RET
AVR实用程序
;范例19 ;等步距线性内插计算子程序
.EQU TBLGTH=10
CHETA: LDI R16,TBLGTH-1 ;r16<--表长(即字数)-1
LDI R31,HIGH(chtbl*2);y0(函数初值)在r14r15,STEP(步长)在r10r11,自变量X在r12r13
LDI R30,LOW(chtbl*2+1);查表指针,首指数据表第1字之高位字节!
RCALL CPMR1 ;X与表中第一个字型数据(X0)比较
BRCC CHRET ;X<X0 查表结束,Y=Y0
CHET1: RCALL CMPR1 ;X与表中下一个数据比较
BRCC NX33 ;X<X(i+1) 找到插值区间
ADD R15,R11 ;否则Y0中加入一个STEP:Yk=Y0+k*step(步距为负时则
;减去|STEP|)
ADC R14,R10
DEC R16
BRNE CHET1 ;未查到表格终值,循环;否则结束,Y取得最大值Yn
CHRET: RET
NX33: SBIW R30,5 ;指针退回(-5),指向Xi
MOV R8,R14
MOV R9,R15 ;保存Y0+i*STEP
RCALL SUBS ;(X-Xi)-->r16r17
MOV R15,R17
MOV R14,R16 ;转入r14r15
RCALL MUL16 ;(X-Xi)*STEP-->r12r13r14r15
MOV R10,R12
MOV R11,R13 ;保存乘积高位字
LPM ;X(i+1)低位字节
MOV R13,R0
ADIW R30,1
LPM ;X(i+1)高位字节
MOV R12,R0
SBIW R30,3 ;指针指向Xi
RCALL SUBS ;X(i+1)-Xi-->r16r17
MOV R12,R10
MOV R13,R11 ;取回乘积高位字
MOV R10,R16
MOV R11,R17 ;X(i+1)-Xi-->r10r11
RCALL DIV165 ;(X-Xi)*STEP/[X(i+1)-Xi]-->r14r15
ADD R15,R9
ADC R14,R8 ;Y0+i*STEP+(X-Xi)*STEP/[X(i+1)-Xi]-->r14r15
RET ;若STEP为负值则改为计算(r8r9)减去(r14r15)之值
CMPR1: LPM ;取数据高位字节
ADIW R30,2 ;指向下一数据的高位字节
CP R0,R12 ;与X高位字节相比较
BRNE CPRT1 ;不相等即转出
SBIW R30,3 ;否则调整指针
LPM ;取数据低位字节
ADIW R30,3 ;指向下一数据的高位字节
CP R0,R13 ;与X低位字节相比较
CPRT1: RET ;以进位C带回比较结果
SUBS: LPM ;计算(X-Xi)或[X(i+1)-Xi]并送入r16r17
MOV R5,R0 ;取Xi低位字节
ADIW R30,1
LPM ;取Xi高位字节
SBIW R30,1 ;仍指向Xi低位字节
SUB R13,R5
MOV R17,R13
SBC R12,R0
MOV R16,R12 ;计算差并将其转入R16R17
RET
;自变量x表长为12字
CHTBL:DW 19214,23404,27600,32799,37009,40211,45414,48618,51821,55029,57787,60070
;步距表长为11字
STEPT: DW 356,366,379,395,415,440,471,509,555,603,657
;不等步距线性内插计算子程序,步距表首址在R6R7中
;自变量X在R12R13之中, 函数初值Y0在R14R15中
;范例20 ;表长(字个数)-1在R16中
CHTSTP: LDI R31,HIGH(chtbl*2)
LDI R30,LOW(chtbl*2+1);查表指针
LDI R16,LOW(stept*2)
MOV R7,R16
LDI R16,HIGH(stept*2)
MOV R6,R16 ;步距表指针
LDI R16,TBLGTH-1 ;r16<--表长(字个数)-1
RCALL CMPR1 ;X与表首数据比较
BRCC CHSTPT ;X<X0 查表结束,有Y=Y0
CHSTP1: RCALL CMPR1 ;否则与表中下一数据比较
BRCC CHSTP3 ;X<X(i+1),找到插值区间!
RCALL GTSTP ;查表取STEP字型变量
ADD R15,R11 ;Y0<--Y0+STEPk
ADC R14,R10
DEC R16
BRNE CHSTP1 ;未查到表格终值循环;否则结束,Y取得最大值Yn
CHSTPT: RET
CHSTP3: SBIW R30,5 ;指针退回,指向Xi低位字节
MOV R8,R14
MOV R9,R15 ;Y0+∑STEPk送入r14 r15
RCALL SUBS ;(X-Xi)->r16r17
MOV R15,R17
MOV R14,R16 ;(X-Xi)转入R14R15
RCALL GTSTP ;查表取STEPi-->R10R11
RCALL MUL16 ;(X-Xi)*STEPi-->R12R13R14R15
MOV R10,R12
MOV R11,R13 ;保存积高位字
LPM
MOV R13,R0
ADIW R30,1
LPM
MOV R12,R0
SBIW R30,3
RCALL SUBS ;(X(i+1)-Xi)-->r16 r17
MOV R12,R10
MOV R13,R11
MOV R10,R16
MOV R11,R17 ;取回积高位字 &(X(i+1)-Xi)-->r10r11
RCALL DIV165 ;(X-Xi)*STEPi/[X(i+1)-Xi]-->r14r15
ADD R15,R9 ;
ADC R14,R8 ;Y0+∑STEPk+(X-Xi)*STEPi/[X(i+1)-Xi]-->r14r15
RET
GTSTP: MOV R5,R6 ;查取STEP字型变量/POINTER in r6r7!
MOV R6,R30
MOV R30,R5
MOV R5,R7
MOV R7,R31
MOV R31,R5 ;(r6r7)<-->Z
LPM
MOV R11,R0
ADIW R30,1
LPM
MOV R10,R0 ;STEPk取到r10r11
ADIW R30,1
MOV R5,R6
MOV R6,R30
MOV R30,R5
MOV R5,R7
MOV R7,R31
MOV R31,R5 ;指针增2后送回r6r7
RET
;范例21 ;功能表程序
FUNC2: LDS R16,$A3 ;use r0,r8,r9,r10,r11,r16&r17/& subprogram dspa
SBR R16,$80 ;功能表程序标志
STS $A3,R16
LDI YH,2
LDI YL,0 ;功能内容表SRAM地址
RCALL FLFUNC ;CLR r27!
LDI R16,2
ST X,R16 ;显示'FUNC.2'
RCALL DL2S
CLR R9 ;功能内容寻址偏移量R9!
CLR R8 ;功能名称寻址偏移量(R8)=(r9)*3
FFUNC0: RCALL DSF_ ;显示'F- '
FF0: RCALL DSPA ;in subprogram dspy clr. r27!
CPI R16,11 ;回车键按下?
BRNE FF2P
FF0C: RCALL COMBNO ;合成功能名称送入r16
CPI R16,20 ;是最后一个功能名称?
BRNE FF1
CLR R9 ;是,两偏移量初始化!
CLR R8
FF1: LDI ZH,HIGH(FTABL*2)
LDI ZL,LOW(FTABL*2);功能名称表指针
ADD ZL,R8
ADC ZH,R27 ;(r27)=0 ALWAYS
LPM
MOV R16,R0
RCALL BRA3A ;分解新功能名称到$6E/$6F
FF0G: LDI R28,0
ADD R28,R9 ;功能内容指针加偏移量
LD R16,Y
LDI R26,$72
RCALL BRAX ;将新功能内容分解到$72/$73
FF0A: RCALL DSPA ;显示新功能名称/内容
CPI R16,11
BRNE FF0B ;回车键按下?
INC R8
INC R8
INC R8 ;是,功能名称寻址偏移量加3
INC R9 ;功能内容寻址偏移量加1
RJMP FF0C ;转回
FF2P: RJMP FF2
FF0B: CPI R16,10
BRNE FF0D
RCALL DSF_ ;清除键按下,清除显示区后,显示‘F-’
FF1B: RCALL DSPA
CPI R16,11
BREQ FF1 ;转恢复当前显示
CPI R16,10
BRCC FF1B
RJMP FF2D ;只有数字键按下才转出去处理
FF0D: CPI R16,10
BRCC FF0A
FF1D: LDI R17,$24 ;
STS $73,R17 ;数字键处理,先在缓存区内放一空白
FF0E: LDS R17,$73
STS $72,R17 ;键入数字左移
STS $73,R16 ;存入新数字
FF0F: RCALL DSPA
CPI R16,10
BREQ FF0G ;清除键按下,恢复显示旧功能内容
BRCS FF0E ;键入数字左移更新
CPI R16,11
BRNE FF0F
LDS R26,$72 ;回车键按下
RCALL COMBA ;合成新功能内容(combin $72&$73 into binary(r16))
MOV R17,R8
INC R17
LDI ZH,HIGH(FTABL*2)
LDI ZL,LOW(FTABL*2)
ADD ZL,R17 ;取当前功能内容下限
ADC ZH,R27
FF1F: LPM
CP R16,R0
BRCS DSER2 ;新功能内容小于下限,错误
INC R17
LDI ZH,HIGH(FTABL*2)
LDI ZL,LOW(FTABL*2)
ADD ZL,R17 ;取当前功能内容上限
ADC ZH,R27
LPM
CP R0,R16
BRCS DSER3 ;新功能内容大于上限,错误
FF7: LDI R28,0
ADD R28,R9 ;功能内容表首地址为$200!
ST Y,R16 ;合法的新功能内容进入功能内容表
INC R9
INC R8
INC R8
INC R8 ;调整偏移量,进入下一个功能显示
RJMP FF0C
FF1P: RJMP FF1
DSER2: RCALL FERR2 ;显示'F Err.2'2秒
RCALL EXCH0
RJMP FF0G ;恢复原数据显示
DSER3: RCALL FERR3 ;显示'F Err.3'2秒
RCALL EXCH0
RJMP FF0G ;恢复原数据显示
FF2: CPI R16,10
BRCS FF2D ;功能键按下,转初始
RJMP FF0
FF2D: LDI R17,$24 ;数字键按下,在显示缓存区内左移
STS $6F,R17 ;
FF3: LDS R17,$6F
STS $6E,R17
STS $6F,R16
FF4: RCALL DSPA
CPI R16,10
BRNE FF41
RCALL DSF_ ;清除数字,显示‘F-’
FF40: RCALL DSPA
CPI R16,11
BREQ FF1P ;转回显示当前功能名称及内容
CPI R16,10
BRCC FF40 ;无效键按下,转回
RJMP FF2D ;否则转数字处理
FF41: BRCS FF3
CPI R16,11
BRNE FF4
RCALL COMBNO ;合成新功能名称
CLR R10 ;功能名称偏移量计数器清除
CLR R11 ;功能内容偏移量计数器清除
SFFLP: LDI ZH,HIGH(FTABL*2)
LDI ZL,LOW(FTABL*2)
ADD ZL,R10
ADC ZH,R27
LPM
CP R0,R16 ;
BREQ SFFND ;在功能名称表中找到新名称
INC R11 ;
INC R10
INC R10
INC R10 ;调整偏移量
LDI R17,60
CP R10,R17 ;功能名称指针偏移量超过59?
BRCS SFFLP ;否,继续查功能名称表
RCALL FERR1 ;查完功能名称表未查到键入功能名称!
RJMP FFUNC0 ;转回恢复原显示
SFFND: MOV R9,R11 ;得到功能内容指针偏移量
MOV R8,R10 ;得到功能名称指针偏移量
RJMP FF0G ;转显示新功能名称及内容
FTABL: .DB 1,0,1,2,1,8,3,0,2,4,0,1 5,1,2,6,0,4,7,1,4,8,1,2,9,2,7,10,1,5,11,1
.DB 5,12,0,5,13,1,2,14,1,7,15,1,10,16,1,4,17,2,4,18,2,5,19,1,2,20,1,3
COMBNO: LDI XL,$6E ;取$6E$6F中的BCD码,合成新功能名称子程序
COMBA: LD R16,X+
CPI R16,$24
BRNE CMBA
CLR R16
CMBA: MOV R0,R16
LSL R16
LSL R16
ADD R16,R0
LSL R16 ;高位BCD乘10
LD R0,X
ADD R16,R0 ;加低位BCD
RET
DSF_: RCALL FIL8 ;准备显示'F- '
LDI R16,$0F
STS $6C,R16
LDI R16,$14
STS $6D,R16
RET
BRA3A: LDI XL,$6E ;二进制数转换为两位BCD码并显示
BRAX: LDI R17,$24 ;十位为0时显示空白
ST X,R17
BRHOUR: CLR R0 ;
BRX0: SUBI R16,10 ;减10
BRCS BRX2
INC R0
RJMP BRX0
BRX2: SUBI R16,-10 ;不够减恢复出十位BCD
TST R0
BREQ BRX1
ST X,R0 ;放入显示区
BRX1: INC R26
ST X,R16
BRART: RET
FERR1: LDI XL,$71 ;显示'F Err.1'
LDI R16,1
ST X,R16
RJMP FER123
FERR2: RCALL MOVE1 ;显示'F Err.2'
LDI R16,2
STS $71,R16
RJMP FER123
FERR3: RCALL MOVE1 ;显示'F Err.3'
LDI R16,3
STS $71,R16
FER123: LDI XL,$6C
LDI R16,$0F
ST X+,R16
LDI R16,$24
ST X+,R16
LDI R16,$0E
ST X+,R16
LDI R16,$1B
ST X+,R16
LDI R16,$3B
ST X+,R16 ;显示'F Err.1/2/3'
LDI R16,$24 ;2秒
STS $72,R16
STS $73,R16
RCALL DL2S
RET
FIL8: LDI R26,8 ;将显示缓存区充空白
MOV R10,R26
LDI R26,$6C
CLR R27
LDI R16,$24
FILP: ST X+,R16
DEC R10
BRNE FILP
RET
FLFUNC: RCALL FIL8 ;准备显示'Func.'
LDS R26,$6C
LDI R16,$0F ;'F'
ST X+,R16
LDI R16,$1E ;'u'
ST X+,R16
LDI R16,$17 ;'n'
ST X+,R16
LDI R16,$40 ;'c.'
ST X+,R16
RET
EXCH0: LDI ZL,$14 ;将显示缓存区内容转移$6C-$73<-->$214-$21B
LDI ZH,2
LDI XL,$6C
EXL: LD R16,X
LD R17,Z
ST X+,R17
ST Z+,R16
CPI R26,$74
BRNE EXL
RET
MOVE1: LDI ZL,$14 ;将显示缓存区内容传送到$214-$21B
LDI ZH,2
LDI XL,$6C
MV1: LD R16,X+
ST Z+,R16
CPI R26,$74
BRNE MV1
RET
;EEPROM 读写程序
;范例22 ;读出EEPROM子程序
REEP: LDI YH,1
LDI YL 0 ;EEPROM 读出首地址:$100
LDI XL,$60 ;读出数据存放首地址:$60
CLR XH
REEP1: SBIC $1C,1 ;查EEWE位,EEWE=1为当前尚有写入操作未结束
RJMP REEP1 ;等待EEWE=0
OUT $1F,YH
OUT $1E,YL ;读出地址写入EEPRO地址寄存器
SBI $1C,0 ;设置读出使能位(EERE)
IN R16,$1D ;从EEPROM数据寄存器中读出数据
ST X+R16 ;存入缓存区
INC YL
BRNE REEP1 ;
INC YH
CPI YH,2 ;EEPROM最末数据(地址为$1FF)读完?
BRNE REEP1
RET
;范例23 ;写入EEPROM子程序
WEEP: LDI YH,1
LDI YL 0 ;EEPROM 写入之首地址:$100
LDI XL,$60 ;写入数据存储区首地址:$60
CLR XH
WEEP1: SBIC $1C,1 ;查EEWE位,EEWE=1为当前尚有写入操作未结束
RJMP WEEP1 ;等待EEWE=0
OUT $1F,YH
OUT $1E,YL ;送写入地址到EEPRO地址寄存器
LD R16,X+ ;取写入数据并调整数据指针
OUT $1D,R16 ;送到EEPROM数据寄存器
SBI $1C,2 ;设置EEPROM写入总使能位EEMWE
SBI $1C,1 ;设置EEPROM写入使能位EEWE
INC YL
BRNE WEEP1
INC YH
CPI YH,2 ;EEPROM最末写入单元地址为$1FF
BRNE WEEP1
RET
;时钟日历芯片62×42×读写程序,时钟日历数据读入到显示缓存区$6C--$73
;范例24 ;USE 8515!使用DSPA子程序
.EQU RTCH=$40 ;rtc地址高八位
RDATE: RCALL BSYT ;初始化,兼冻结RTC
LDI XL,$6D ;数据缓存区首地址
LDI YL,$06 ;首指日单元
RDLP: LD R16,Y+ ;$6b 6c 6d 6e 6f 70 71 72 73
ANDI R16,15 ; 2 9(D) - 1 0(M) - 0 2(Y)
CPI R16,10
BRCS RDL1
ANDI R16,$7F ;容错处理
RDL1: ST X,R16$
DEC R26
CPI R26,$6B
BRNE RDLP1
LDI XL,$70
RDLP1: CPI R26,$6E
BRNE RDLP2
LDI R16,$14 ;送‘-’到$6E单元
ST X,R16
LDI XL,$73
RDLP2: CPI R26,$71
BRNE RDLP
LDI R16,$14
ST X,R16 ;送‘-’到$71单元并结束子程序
RDINVL: RJMP WCRT
RTIME: RCALL FIL2 ;请除缓存区
RCALL BSYT
LDI XL,$73
LDI YL,$02 ;指向分单元(只读时分)
RCL: LD R16,Y+
ANDI R16,15
CPI R16,10
BRCS RCL0
ANDI R16,$7F ;容错处理
RCL0: ST X,R16
DEC R26
CPI R26,$71
BRNE RCL1
LDI R16,$14 ;写入‘-’
ST X,R16
DEC R26
RCL1: CPI R26,$6E ;$6c 6d 6e 6f 70 71 72 73
BRNE RCL ; 1 6 - 3 5
CLR R16
ST Y,R16
LDS R17,$9FFB ;时制存储单元
LDS R16,$6f
SWAP R16
LDS R15,$70
ADD R16,R15 ;合成小时
SUBI R16,$24 ;模24
RCALL SUDAA ;BCD码减法调整
BRCC RCL2 ;够减,转
SUBI R16,-36 ;否则恢复被减数
RCL2: CPI R17,2
BRNE PRTD1 ;24小时制,转
SUBI R16,$12
RCALL SUDAA
BRCC PRTD1 ;12小时制处理
SUBI R16,-18
PRTD1: MOV R17,R16
SWAP R16
ANDI R16,$0F
ANDI R17,$0F
STS $6F,R16
STS $70,R17 ;小时数据送入显示区
RJMP WCRT
WDATE: RCALL WRTC ;将显示缓存区中日期数据写入RTC
LDI XL,$6F
LD R16,X
CPI R16,10
BRCC WDRT ;非法数据,退出
LDI YL,6
WDLP: LD R16,X
DEC R26
CPI R16,$24 ;SPC?
BRNE WD0
CLR R16 ;变为0
WD0: ST Y+,R16
CPI R26,$6D
BRNE WD1 ;$6d 6e 6f 70 71 72 73
LDI XL,$71 ; 2 9(日) 1 1(月) 0 2 (年)
RJMP WDLP
WD1: CPI R26,$6f
BRNE WD2
LDI R26,$73
WD2: CPI R26,$71
BRNE WDLP
LWDRT: RJMP WCRT
WTIME: RCALL WRTC ;将显示缓存区中时间数据写入RTC
LDI R26,$73
LD R16,X
CPI R16,10
BRCC WCRT ;非法数据,退出
LDI YL,2
WLOP: LD R16,X
CPI R16,$24
BRNE WT1
CLR R16 ;容错处理
WT1: ST Y+,R16
DEC R26
WLP: CPI R26,$6F
BRNE WLOP ;$6E 6f 70 71 72 73
WCRT: CLR R16 ; 1 5 3 8
LDI YL,$0D
ST Y,R16 ;解除对RTC之冻结
IN R16,MCUCR
CBR R16,$C0
OUT MCUCR,R16 ;禁止读写外部RAM
RET
;对rtc初始化/冻结时钟
BSYT: LDI YH,RTCH ;rtc地址高八位
LDI YL,$0D ;指向D寄存器
IN R16,MCUCR
SBR r16,$C0 ;允许读写外部RAM并选一个时钟周期等待时间
OUT MCUCR,R16
LDI R16,5 ;设置冻结位和中断申请位
ST Y,R16
CLR XH
BSRT: RET
;写RTC初始化子程序
WRTC: RCALL BSYT
LDI YL,$0E ;指向寄存器E
LDI R16,6
ST Y+,R16 ;指向寄存器F
LDI R16,1 ;设置时制位
ST Y,R16
LDI R16,4 ;选24小时制
ST Y,R16
CLR R16 ;请除时制位
ST Y,R16
RJMP BSYT
;范例25 ;显示保护子程序/晶振4MHZ
DSPRV: LDI R16,HIGH(ramend)
OUT SPH,R16
LDI R16,LOW(ramend)
OUT SPL,R16
CLR R2 ;调DSPY次数寄存器清除
WDR
LDI R16,$0D ;启动看门狗,溢出时间为0.49s
OUT WDTCR,R16 ;写入看门狗控制寄存器
CLR XH
LDI XL,$6C
DSPVL: ST X+,XH ;清显示缓存区($6c-$73)
CPI XL,$74
BRNE DSPVL
DSPV0: LDI R16,$66
MOV R9,R16
LDI R16,$82 ;$6582=25986,高位字节增1为$66
MOV R10,R16 ;调25986次DSPA耗时120s
DSNEX: LDI XL,$74 ;将显示区十进制数据增1以演示数据变化
DSLOP: LD R16,-X ;实用时可以采样数据更新显示(参考范例96)
INC R16
ST X,R16
CPI R16,$0A
BRNE DSPRV1
CLR R16
ST X,R16
CPI R26,$6C
BRNE DSLOP ;增1后如有进位则调整
DSPRV1: DEC R10
BRNE DSPGN
DEC R9
BRNE DSPGN ;2分钟定时到?
DSCLOS: RCALL FIL2 ;将显示缓存区充入空白($24)
RCALL DSPA ;其效果相当于关显
SBRC R16,7
RJMP DSCLOS
RJMP DLFUNC ;有键按下,转出;否则继续关显
DSPGN: RCALL DSPA ;未到,显示数据
SBRC R16,7
RJMP DSNEX ;无键按下,继续显示
DLFUNC: CPI R16,12 ;关显键键值为12
BEEQ DSCLOS ;关显键按下,转关闭显示
;.
;.
;.
;.
;(其他键值处理,参考范例26 DEALKY程序)
RJMP DSPV0 ;执行功能后转入二分钟定时
;范例26 ;键值处理程序
DEALKY: LDI R16,HIGH(ramend)
OUT SPH,R16
LDI R16,LOW(ramend)
OUT SPL,R16
CLR R2 ;调DSPY次数寄存器清除
WDR
LDI R16,$0D ;启动看门狗,溢出时间为0.49”
OUT WDTCR,R16 ;写入看门狗控制寄存器
DEALK0: RCALL DSPA
SBRC R16,7
RJMP DEALK0 ;无键按下,反复查询
CPI R16,10
BRCC FNCKY ;功能键按下,跳转
RCALL FIL2 ;键值<10为数字键 ,先清除显示缓存区
NUMKY: RCALL LSDD8 ;8位数字左移,新键值加入序列尾
DSLP: RCALL DSPA
SBRC R16,7
RJMP DSLP ;无键按下,继续显示
CPI R16,11
BRCS NUMKY ;键入数字形成左移序列/按清除键则清除所有键入数据
BRNE DSLP ;键值大于11无效
;11为回车键,对键入数字进行处理(如将其两两合并为BCD
;码,再转为二进制数等)
RJMP DEALK0 ;转回
FNCKY: SUBI R16,10 ;功能键散转处理,先计算键值偏移量
LDI R31,HIGH(FKYTB)
LDI R30,LOW(FKYTB);散转表表首
ADD R30,R16
CLR R16
ADC R31,R16 ;偏移量加入指针
IJMP ;散转
FKYTB: RJMP CLTTL ;10:清除累加和
RJMP DSTTL ;11:显示累加和
RJMP DSCLS ;12:关显示
RJMP SLFTS ;13:自检
RJMP FDPAP ;14:打印机走纸
RJMP PRSMP ;15:打印采样
RJMP PRTTL ;16:打印累加和
RJMP DSCLK ;17:显示系统时钟
;............. ;.........
;............. ;.........
CLTTL: ;............. ;程序内容略
;.............
RJMP DEALK0 ;程序执行完毕,转回
DSTTL: RCALL BRTTL ;分解累加和送显示缓存区
RCALL DSPA ;显示累加和
SBRC R16,7
RJMP DSTTL ;任一键按下,结束显示累加和
RJMP DEALK0 ;程序执行完毕,转回
DSCLS: RJMP DSCLOS ;转去关显示
SLFTS: ;.............
;.............
RJMP DEALK0 ; 自检程序执行完毕,转回
FDPAP: ;.............
;.............
RJMP DEALK0 ; 走纸程序执行完毕,转回
PRSMP: ;.............
;.............
RJMP DEALK0 ; 打印采样程序执行完毕,转回
PRTTL: ;.............
;.............
RJMP DEALK0 ;打印累加和程序执行完毕,转回
DSCLK: RCALL BRCLK ;分解系统时钟送入显示缓存区
RCALL DL1S ;延时1秒
RCALL DSPA ;显示时钟
SBRC R16,7 ;任一键按下,结束显示时钟
RJMP DSCLK
RJMP DEALK0 ;程序执行完毕,转回
;.............
;............. ;其他功能键处理略
;.............
;范例27 ;主显子程序
DSPA: SBRC R16,7 ;USE R0,R2,R11,R12,r13,r14,r15,r16,r17&Z,X POINTERS
RJMP DSA2 ;无键按下,跳转
DSA0: CLR R12
INC R12 ;有键按下,将计数器置1
DSA1: RCALL DSPY
DEC R12
BRNE DSA1 ;等待键释放
DSA2: RCALL DSPY
LDS R16,$A3
SBRS R16,7 ;有进入功能表程序标志?
RET ;没有返回
SBI PORTA,0 ;
SBIS PINA,0 ;退出功能表程序吗?
RET
CBR R16,$80 ;是,清除进入功能表程序标志($A3,7)
STS $A3,R16
RCALL FIL2
LDI R16,$0F ;'F'
STS $6C,R16
LDI R16,$0E ;'E'
STS $6E,R16
LDI R16,$17 ;'n'
STS $6F,R16
LDI R16,$0D ;'d'
STS $70,R16 ;显示‘F End'
RCALL DL2S ;2秒后
RJMP DIPA1 ;转到主程序(包括对堆栈)初始化
DL2S: RCALL DL1S ;延时2秒子程序
DL1S: LDI R16,217 ;延时1秒子程序/4MHz clk
MOV R11,R16 ;4.618×217=1000ms
DLCOM: RCALL DSPA
DEC R11
BRNE DLCOM
RET
;范例28 ;基显子程序,显示缓存区:$6C--$73,执行时间4.618ms/晶振4MHZ
;主程序应对看门狗初始化,设置溢出时间为0.49秒!
DSPY: LDI R17,$0F ;使用R0,R2,R12,R13,R14,R15,R16&R17/z&x pointer!
OUT DDRA,R15 ;PA7--PA4为键列值输入
CLR R15
COM R15
OUT DDRB,R15
OUT DDRC,R15 ;口B:段选输出,口C:位选输出
OUT PORTC,R15 ;关显
DPY1: LDI R26,$6C ;指向显示缓存区首址:$6C
CLR R27
LDI R17,$7F
MOV R13,R17 ;位选初始化(首显最高位)
L0D: LD R17,X+
LDI R31,HIGH(table*2)
LDI R30,LOW(table*2)
ADD R30,R17
ADC R31,R27
L0C: LPM ;取段选码
OUT PORTB,R0 ;送段选口
OUT PORTC,R13 ;位选口
SEC ;
ROR R13 ;指下一位位选
LDI R17,3 ;4MHz(6 if 8MHz)
CLR R14
DLOP: DEC R14
BRNE DLOP
DEC R17
BRNE DLOP ;延时0.5762毫秒
IN R16,PORTA
ORI R16,$F0 ;保护PA3--PA0输出
OUT PORTA,R16 ;提拉PA7-PA4
IN R14,PINA ;读入列值
NEX: ROL R14 ;use high 4bits!
BRCC L1 ;有键按下,跳转
NEX1: INC R17 ;指向下一列
CPI R17,4
BRNE NEX ;各列都查完?
NEX2: SER R17
OUT PORTC,R17 ;将$FF写入位选口(关显)
CPI R26,$74
BRNE L0D ;每位LED都显示一遍??
MOV R16,R15 ;YES
INC R2 ;增一调DSPY次数寄存器
MOV R17,R2
CPI R17,100 ;到100次?
BRNE NEX3
CLR R2 ;清除看门狗定时器时间到计数器/4.618ms×100=0.462s(<0.49s)
WDR ;看门狗定时器复位
NEX3: RET
L1: LDS R16,$73 ;计算键值代码/查键值
SUB R16,R26 ;$73-(r26)-->r16
LSL R16
LSL R16 ;行值*4
ADD R16,R17 ;键值代码=行值*4+列值
LDI R30,LOW(TABL0*2)
ADD R30,R16
LDI R31,HIGH(TABL0*2)
ADC R31,R27
LA00: LPM ;查出键值
MOV R15,R0 ;放在R15
LA10: INC R12 ;计数器增1以备判断键释放
RJMP NEX1 ;转回查下一列
TABL0: .DB 10,0,11,20,1,2,3,16,4,5,6,22,7,8,9,18,12,15,19,23,14,17,21,13
TABLE: .DB $3F,$06,$5B,$4F,$66,$6D,$7D,$07,$7F,$67,$77,$7C,$39 ;0--C
.DB $5E,$79,$71,$6F,$74,$04,$1F,$40,$38,$37,$54,$5C ;'d'---'o'
.DB $73,$67,$50,$6D,$78,$1C,$3E,$7E,$F8,$6E,$49,$00
.DB $48,$52,$D3,$76 ;$25(=),$26(/)$27(?) END AT $28(H)
.DB $BF,$86,$DB,$CF,$E6,$ED,$FD,$87,$FF,$E7;THE 0.($29)--9.($32)
.DB $D7,$C9,$80 ;THE 'X.' 'Z.' &'.'($33--$35)
.DB $DE,$EF,$B8,$F3,$E7,$D0,$DC,$ED,$86,$F9,$B9H,$F7,$F1,$B7,$D4
;the d.,g.,L.,p.,q.,r.,o.,s.,l.,E.,C.,A.,F.,M.,n.(36--44h)
;范例29 ;键入数字序列左移处理子程序
LSDD8: LDI R26,$6C ;8bcd码($6C--$73H)
LDS R27,$A3
CBR R27,8 ;清$A3,3
STS $A3,R27
CLR R27
CPI R16,10 ;10为清除键
BRNE DDL
RCALL FIL2 ;清除显示缓存区($6c-$73)!
LDS R16,$A3
SBR R16,8
STS $A3,R16 ;建清除显示缓存区标志$A3,3=1
RET
DDL: INC R26 ;数字键按下,序列左移
LD R16,X ;
SUBI R16,$29 ;数字带小数点?
BRCC DD4 ;若带则将其复原(参考DSPY子程序段码表)
SUBI R16,$D7 ;恢复
DD4: ST -X,R16 ;移入左邻单元
DD5: INC R26
CPI R26,$73
BRNE DDL ;各数字都左移了一位?
ST X,R15 ;新键入数字进入数字序列末位
LDI R26,$6C
DEL: LD R16,X
CPI R16,10 ;是BCD码?
BRCS DEL2
CPI R16,$29
BRCC DELRT ;大于$29为错误!
DELA: INC R26 ;0--9/$24/$14为有效!
CPI R26,$73
BRNE DEL ;缓存区检查完毕?
RJMP DELRT
DEL2: CPI R16,0
BRNE DELRT
LDI R16,$24 ;0改为空白
ST X,R16
RJMP DELA ;
DELRT: LDS R16,$A0 ;小数点位置单元
TST R16
BREQ DDRET ;($a0)=0,无小数点
NEG R16
ADD R16,$73
MOV R26,R16 ;找到缓存区内带小数点的数据位
LD R16,X
SUBI R16,$D7 ;加上小数点
ST X,R16
CPI R16,$4D ;在空白码加了小数点($24(空白)+$29=$4d)?
BRNE STLR1
LDI R16,$29
ST X,R16 ;是,将其改为'0.'
STLR1: CPI R26,$73
BREQ DDRET ;并将其后所有空白都改为0
INC R26
LD R16,X
CPI R16,$24
BRNE DDRET
CLR R16
ST X,R16
RJMP STLR1
DDRET: RET
FIL2: LDI R26,8 ;在显示缓存区内填充空白
MOV R14,R26
FIL2A: LDI R26,$6C
FIL: CLR R27
LDI R16,$24
FILP: ST X+,R16
DEC R14
BRNE FILP
RET
;范例30 ;双键输入检查数据子程序,Ky1数据键/Ky2回车键
KYIN2: LDI R26,$60 ;寄存器地址:portb:$18/ddrb:$17/pinb:$16
CLR R27 ;指向数据区首地址
CBI DDRB,7
CBI DDRB,6 ;pb7和pb6皆为输入口
SER R17
OUT DDRC,R17 ;c口为数据显示口
LA0: LD R17,X ;取数据
CPI R17,$0A
BRCS LA1
CLR R17
LA1: LDI R31,HIGH(table*2)
LDI R30,LOW(table*2);DSPY段选码表
ADD R30,R17
ADC R31,R27
LPM
COM R0 ;段选码取出并取反
OUT PORTC,R0 ;送C口
SBI PORTB,7
SBIC $16,7
RJMP NXA1 ;数字键未按下,转
RCALL DL50 ;否则延时
XA2: SBI PORTB,6
SBIC $16,6
RJMP XA0 ;只有数字键按下,转
XA20: RCALL DL50 ;两键都按下,先延时50mS
SBI PORTB,6
SBIS $16,6
RJMP XA20
SBI PORTB,7
SBIS $16,7
RJMP XA20 ;等两键都释放
RCALL DL50
XA21: SBI PORTB,6
SBIS $16,6
RJMP XA21 ;等待释放
SBI PORTB,7
SBIS $16,7
RJMP XA21 ;再次等待释放
RJMP NXA6 ;先按数字键,再按回车键,待2都键释放后退出子程序
XA0: SBI PORTB,7
SBIS $16,7
RJMP XA2 ;等待数字键释放
XA1: RCALL DL50 ;延时
SBI PORTB,7
SBIS $16,7
RJMP XA1 ;再次等待释放
INC R17 ;数字增1
CPI R17,10
BRCS NXA1
CLR R17 ;超过10,将键值归为0
NXA1: SBI PORTB,6
SBIC $16,6
RJMP LA1 ;回车键也未按下,重新查键
RCALL DL50 ;延时
NXA3: SBI PORTB,6
SBIS $16,6
RJMP NXA3 ;再次等待回车键释放
RCALL DL50
SBI PORTB,6
SBIS $16,6
RJMP NXA3
ST X+,R17 ;数字转入缓存区
SER R17
OUT PORTB,R17 ;关显
RCALL DL50 ;
CPI R26,$70 ;到规定数字个数?
BRNE LA0 ;
LDI R17,$86 ;显示'E'nd
OUT PORTC,R17 ;
NXA4: SBI PORTB,6
SBIS $16,6
RJMP NXA5 ;回车键按下,转
SBI PORTB,7
SBIC $16,7 ;数字键按下,转
RJMP NXA4 ;否则反复查键
NXA40: RCALL DL50
SBI PORTB,7
SBIS $16,7
RJMP NXA40
SBI PORTB,7
SBIS $16,7
RJMP NXA40 ;等待键释放
RJMP KYIN2 ;转检查键入数据
NXA5: RCALL DL50
SBI PORTB,6
SBIS $16,6
RJMP NXA5
SBI PORTB,6
SBIS $16,6
RJMP NXA5 ;等回车键释放
NXA6: SER R17
OUT PORTB,R17 ;关显,结束子程序
RET
DL50: ;RCALL DL25 ;延时50毫秒子程序/8Mhz(去掉指令前“;”号)
DL25: CLR R14 ;延时50毫秒子程序/4Mhz
CLR R15
DL50L: DEC R15
NOP
BRNE DL50L
DEC R14
BRNE DL50L
RET
;范例31
LPRNT: SER R17 ;宽行打印机检测及控打程序
OUT DDRC,R17 ;C口为打印机输出口!
SBI DDRD,7
CBI DDRD,3 ;pd7为选通输出口,pd3(INT1)查忙输入口
SBI PORTD,3
SBIC PIND,3 ;查打印机忙信号
RJMP ERR5 ;打印机尚未工作忙信号即已为高,打印机不能打印
LDI R17,$0D ;写回车命令给打印机
OUT PORTC,R17
CBI PORTD,7 ;发出选通信号
NOP
NOP
NOP
SBI PORTD,7 ;strobe
LDI R16,50
TSPRT: SBI PORTD,3
SBIc PIND,3
RJMP LPRT2 ;50次内忙信号高起来为正常
DEC R16 ;否则为非正常状态
BRNE TSPRT
ERR5: LDI R16,5
RCALL ERRX ;显示5号错误
RJMP DIPA1 ;转主程序初始化
LPRT2: LDI R25,1
CLR R24 ;point to $100
LDI R17,$80
OUT GIMSK,R17 ;允许int1中断
LDI R17,$0A
OUT MCUCR,R17 ;INT1下降沿中断
SEI ;general interrupt enable
RET
EX_INT1:PUSH R26
PUSH R27
IN R27,SREG
PUSH R27
PUSH R17 ;保护现场
MOV R27,R25 ;取数据指针
MOV R26,R24
LD R17,X+ ;
MOV R25,R27
MOV R24,R26 ;增1后将指针送回
CPI R17,3 ;是停止符?
BRNE INT1SD
CLR R17
OUT GIMSK,R17 ;禁止INT1中断
RJMP INT1ED
INT1SD: OUT PORTC,R17 ;打印数据输出到打印口
CBI PORTD,7 ;clr ($12,7)
NOP
NOP
NOP
SBI PORTD,7 ;向打印机发出选通
INT1ED: POP R17
POP R27
OUT SREG,R27
POP R27
POP R26 ;恢复现场
RETI
;范例32 ;步进电机控制程序
.ORG 0
STRT10: RJMP RST10 ;8535/8515/晶振4MHZ
.ORG $011
RST10: LDI R16,HIGH(ramend)
OUT SPH,R16
LDI R16,LOW(ramend)
OUT SPL,R16
SER R16
OUT DDRB,R16 ;B口为输出
LDI R17,8
OUT PORTB,R16 ;接通总开关
LDI R16,50 ;50次基本运作
RCALL DELAY5 ;延时5毫秒
LOOPX: LDI R17,$68 ;step1时序脉冲控制
OUT PORTB,R17
RCALL DELAY2 ;延时2毫秒
LDI R17,$38 ;step2时序脉冲控制
OUT PORTB,R17
RCALL DELAY2 ;延时2毫秒
LDI R17,$98 ;step3时序脉冲控制
OUT PORTB,R17
RCALL DELAY2 ;延时2毫秒
LDI R17,$C8 ;step4时序脉冲控制
OUT PORTB,R17
RCALL DELAY2 ;延时2毫秒
DEC R16
BRNE LOOPX ;到50次?
LDI R17,8
OUT PORTB,r17 ;关闭各相位开关
RCALL DELAY5
RCALL DELAY5 ;延时10毫秒
CLR R17
OUT PORTB,R17 ;关闭所有相位开关和总开关
HH0: RJMP HH0 ;踏步
DELAY1: LDI R17,$06 ;延时1毫秒
MOV R15,R17 ;1000/0.75=1333=$535,外層计数器装入$06
LDI R17,$35 ;DEC+BRNE=0.75微秒
RJMP DLCOM
DELAY2: LDI R17,$0B ;延时2毫秒
MOV R15,R17 ;2000/0.75=2666=$0A6A,外層计数器装入$0B
LDI R17,$6A
DLCOM: DEC R17
BRNE DLCOM
DEC R15
BRNE DLCOM
RET
DELAY5: LDI R17,$1B ;延时5毫秒
MOV R15,R17 ;5000/0.75=6666=$1A0A,外層计数器装入$1B
LDI R17,$0A
RJMP DLCOM
;范例33
.ORG 0 ;8515采用定时器中断输出时序脉冲方式控制电机转动
STRT11: RJMP RST11 ;晶振4MHZ
.ORG $007
RJMP T0_OVF ;中断服务程序与STRT12共用
.ORG $00D
RST11: LDI R17,HIGH(ramend)
OUT SPH,R17
LDI R17,LOW(ramend)
OUT SPL,R17
LDI R17,$68
MOV R7,R17 ;初始脉冲为0B01101000
SER R17
OUT DDRB R17 ;B口为输出
LDI R17,N ;运作次数N(N>0)
RCALL STPDRV ;初始化子程序
HH20: RJMP HH20 ;实用时改为具体的背景程序!
STPDRV: TST R17
BRNE STPDR1
INC R17 ;N=0时,将其改为1
STPDR1: MOV R6,R17
INC R6 ;N+1-->r6(max.is 256;“植树问题”,N必需增1!
LDI R17,$A4
CBR R17,$20
STS $A4,R17 ;清除连续转动电机标志
LDI R17,$08
OUT PORTB,R17 ;接通总开关
LDI R17,4 ;0B00000100/ 256分频(4兆/256=1兆/64)
OUT TCCR0,R17
LDI R17,178 ;78*64=4.992ms
OUT TCNT0,R17 ;时间常数,首定时为5毫秒
LDI R17,$02
OUT TIMSK,R17 ;允许T/C0溢出中断
SEI
HH21: SJMP HH21
;范例34
.ORG $000 ;步进电机手动控制程序(8515)晶振4MHZ
STRT12: RJMP RST12
.ORG $007
RJMP T0SEV
.ORG $00D
RST12: LDI R17,HIGH(ramend)
OUT SPH,R17
LDI R17,LOW(ramend)
OUT SPL,r17
LDI R17,$68
MOV R7,R17 ;第一个时序脉冲
LDI R17,$F8
OUT DDRB,R17 ;PB7-PB3输出,PB2-PB0输入
CLR R17
OUT PORTB,R17 ;输出为低电平
LDS R17,$A4
SBR R17,$20 ;设置连续转动标志
CBR R17,$40 ;设置电机正转标志
TSTLP1: SBI PORTB,1 ;PB1接地,正转
SBIS PINB,1
RJMP TSTL11 ;
TSTL10: SBI PORTB,2
SBIC PINB,2 ;PB2接地,反转
RJMP TSTLP1 ;PB1,PB2都未接地,反复查询
SBR R17,$40 ;设置电机反转
TSTL11: STS $A4,R17 ;保存标志
CLR R6
INC R6 ;R6中装入1,减一次即为0!
LDI R17,$08
OUT PORTB,R17 ;接通总开关
LDI R17,4 ;0B00000100/256分频(256/4=64微秒)!
OUT TCCR0,R17
LDI R17,178 ;178之补为78,78*64=4.992ms
OUT TCNT0,R17 ;
LDI R17,$02
OUT TIMSK,R17 ;允许T/C0中断(toie1=$39,7 toie0=$39,1)
;8535,toie1:$39,2 toie0:$39,0
SEI
TSTLP2: SBI PORTB,1
SBI PORTB,2
IN R17,PINB
ANDI R17,6
CPI R17,6
BRNE TSTLP2 ;两开关未全部打开,查询等待
LDS R17,$A4
CBR R17,$20 ;清除连续转动标志
STS $A4,R17 ;
TSTLP3: IN R17,TIMSK
SBRC R17,1 ;已禁止8515中断?(8535:timsk,0)
RJMP TSTLP3 ;未,查询等待
RJMP RST12
T0_OVF: PUSH R17 ;电机控制中断服务子程序
IN R17,SREG
PUSH R17
LDS R17,$A4
SBRC R17,7
RJMP T0SV2 ;$A4,7:关电机前10毫秒延时标志
MOV R17,R7
CPI R17,$68
BRNE T0SV0
LDS R17,$A4
SBRC R17,5
RJMP T0SV0 ;电机连续转动,不减R6
DEC R6 ;R6减为0,将停止电机
BREQ T0SV1 ;
T0SV0: LDI R17,225 ;每步进延时(256-225)*64=1.984MS err.<0.8%
OUT TCNT0,R17 ;
OUT PORTB,R7 ;步进控制脉冲输出
LDS R17,$A4
SBRC R17,6
RJMP T0SVA ;$A4,6=1 为连续反转
CLC
SBRC R7,4 ;组织下一步控制脉冲
SEC
ROR R7
LDI R17,$08 ;正转
OR R7,R17 ;01101***->00111***->10011***->11001***->01101***.......
RJMP T0RET
T0SVA: MOV R17,R7 ;
SBR R17,$04
ROL R17 ;组织下一步控制脉冲(反转)
BRCS T0SVB
CBR R17,$10 ;01101***->11001***->10011***->00111***->01101***.......!
T0SVB: MOV R7,R17
RJMP T0RET
T0SV1: LDS R17,$A4
SBR R17,$80
STS $A4,R17 ;总开关关断前10毫秒延时标志
LDI R17,$08
OUT PORTB,R17 ;关断4个相位开关
LDI R17,100 ;156(256-100)*64=9.984ms
OUT TCNT0,R17 ;
RJMP T0RET
T0SV2: LDI R17,$07
OUT PORTB,R17 ;关闭所有开关
CLR R17
OUT TCCR0,R17 ;关T/C0中断
OUT TIMSK,R17
LDS R17,$A4
CBR R17,$C0
STS $A4,R17 ;清除10毫秒延时和反向转动标志
T0RET: POP R17
OUT SREG,R17
POP R17
RETI
;精确定时及时钟日历走时子程序
;范例35
.EQU DTPNT=$75 ;年年月日时分秒(from $7B to $75)
.ORG $000
STRT20: RJMP RST20 ;晶体实测频率4.000119MHZ
.ORG $006 ;8515 t1 overflow INT.vector
RJMP T1_OVF
.ORG $00D
RST20: LDI R16,HIGH(ramend)
OUT SPH,R16
LDI R16,LOW(ramend)
OUT SPL,R16
LDI R16,2 ;8分频,4000119/8=500015
OUT TCCR1B,R16
LDI R16,$5E ;500015=65536*8-24273=8*$10000-$5ed1/TCC=$5Ed1
OUT TCNT1H,R16 ;
LDI R16,$D1 ;
OUT TCNT1L,R16 ;将TCC写入TCNT1
LDI R16,$80
OUT TIMSK,R16 ;允许T/C1溢出中断
LDI R16,8 ;8次中断出秒号
MOV R6,R16
SEI
HH10: RJMP HH10 ;可改为具体的实用程序
T1_OVF: PUSH R16
PUSH R17
IN R7,SREG
DEC R6 ;到8次中断?
BRNE GOON1
IN R17,TCNT1L ;*
IN R16,TCNT1H ;*读回TCNT1自然计数值
SUBI R17,$2F ;*$5ED1之补为$A12F,以减法替代加法修正TCC
SBCI R16,$A1 ;*减去$A12E可不做下面的加1修正
SUBI R17,$FF ;*8条修正指令占用一个计数单位时间
SBCI R16,$FF ;*修正后TCC=$5ED1+(TCNT1)+1
OUT TCNT1H,R16 ;*
OUT TCNT1L,R17 ;*将修整后TCC写入TCNT1
LDI R16,8
MOV R6,R16 ;重装中断次数8
;.
;.
RCALL ACLK ;时钟走时
GOON1: POP R17
POP R16
OUT SREG,R7
RETI
;范例36
.EQU DTPNT=$75 ;yyyy mm dd hh mm ss(from $7B--$75)
.ORG $000 ;晶体实测频率8.000267MHZ,8分频
;INT(8000267/8)=1000033
STRT21: RJMP STRT21
.ORG $006 ;8515 t1 overflow INT. vector
RJMP T1_OVF
.ORG $00D
STRT21: LDI R16,HIGH(ramend)
OUT SPH,R16
LDI R16,LOW(ramend)
OUT SPL,R16
LDI R16,2
OUT TCCR1B,R16 ;8分频
LDI R16,1 ;1000033=62332*15+65053
;=($10000-$0C84)*15-$10000-$1E3
OUT TCNT1H,R16 ;主常数62332(补码为$0C84) 补尝常数TCC=$01E3
LDI R16,$E3 ;$FE1D=65053\65053+62332*15=1000033
OUT TCNT1L,R16
CLR R16
OUT TCCR1A,R16 ;DISABLE CMPA/CMPB/PWM!
LDI R16,$80 ;8515
OUT TIMSK,R16 ;允许T/C1溢出中断
LDI R16,16 ;16次中断
MOV R6,R16
SEI
HH11: RJMP HH11 ;
T1_OVF: PUSH R17
PUSH R16
IN R7,SREG
DEC R6 ;中断次数到?未到转装入主常数
BRNE COMP ;否则重装入TCC
IN R17,TCNT1L ;*
IN R16,TCNT1H ;*读回自然计数值
SUBI R17,$1D ;*
SBCI R16,$FE ;*减去TCC之补码
SUBI R17,255 ;*再加1
SBCI R16,255 ;*修正后TCC=$01E3+(TCNT1)+1
OUT TCNT1H,R16 ;*
OUT TCNT1L,R17 ;*
LDI R16,16
MOV R6,R16 ;重写中断次数
;.
;.
RCALL ACLK ;时钟走时
RJMP GOON2
COMP: IN R17,TCNT1L ;*
IN R16,TCNTIH ;*读回TCNT1自然计数值
SUBI R17,$7C ;*先减去$0C84$'补码$F37C
SBCI R16,$F3 ;*再作加1补偿
SUBI R17,$FF ;*
SBCI R16,$FF ;*修整后重装值=[$0C84+(TCNT1)+1]
OUT TCNT1H,R16 ;*
OUT TCNT1L,R17 ;*
GOON2: POP R16
POP R17
OUT SREG,R7
RETI
;范例37 ;8515使用T/C0定时,64分频,晶振频率4000131HZ
.ORG $000
.EQU DTPNT=$75
STRT22: RJMP RST22
.ORG $007
RJMP T0_OVF ;INT(4000131/64)=62502=245*256-218
.ORG $00D
RST22: LDI R16,245 ;245次中断
MOV R6,R16
LDI R16,3
OUT TCCR0,R16 ;主频FCK(4000131HZ)64分频
LDI R16,$02
OUT TIMSK,R16 ;允许T/C0溢出中断
LDI R16,218
OUT TCNT0,R16 ;TCC=218
SEI
HH12: RJMP HH12 ;
T0_OVF: IN R7,SREG
DEC R6
BRNE DECL1 ;
IN R16,TCNT0 ;1秒时间到!
SUBI R16,38 ;218之补
OUT TCNT0,R16 ;
LDI R16,245
MOV R6,R16 ;重装中断次数
RCALL ACLK ;时钟走时
DECL1; OUT SREG,R7
RETI
;范例38 ;8535异步时钟定时程序
.ORG $000 ;时钟频率32768HZ
.EQU DTPNT=$75
STRT23: RJMP RST23
.ORG $004
RJMP T2_OVF
.ORG $011
RST23: LDI R16,8
OUT ASSR,R16 ;选异步时钟
LDI R16,5
OUT TCCR2,R16 ;128分频
CLR R16
OUT TCNT2,R16 ;时间常数256($00)
LDI R16,$40
OUT TIMSK,R16 ;允许T/C2溢出中断
;............
SEI
HH13: LDI R16,$70 ;掉电休眠模式
OUT MCUCR,R16
SLEEP ;进入休眠
RJMP HH13 ;
T2_OVF IN R7,SREG ;
RCALL ACLK ;时钟走时
;...........
OUT SREG,R7
RETI
;范例39
ACLK: PUSH R16
PUSH R27
PUSH R26
PUSH R7
LDI R26,LOW(dtpnt);
LDI R27,HIGH(dtpnt);时钟日历单元指针
RCALL DHM3 ;秒单元加1调整
CPI R16,$60 ;
BRNE COM0 ;未到60秒返回
RCALL DHM ;分单元加1调整
CPI R16,$60
BRNE COM0 ;未到60分返回
RCALL DHM ;时单元加1调整
CPI R16,$24
BRNE COM0 ;未到24时返回
RCALL DHM ;日单元加1调整
SUBI R16,$29
BRCS COM0 ;小于29返回
BRNE T30 ;转继续测试30/31/32日
ADIW R26,1 ;29,指向月
LD R16,X
CPI R16,2
BRNE COM0 ;非二月返回
ADIW R26,1 ;指向年
LD R16,X ;取年十个位
TST R16
BRNE TYLB
ADIW R26,1
LD R16,X ;年十个位为0,取年千百位
TYLB: SWAP R16
ANDI R16,15
MOV R7,R16
LSL R7
LSL R7 ;高位BCD乘4
ADD R16,R7 ;乘5
LSL R16 ;乘10
LD R7,X ;加个位BCD
ADD R16,R7 ;年十个位(千百位)转成二进制数
ANDI R16,3 ;该二进制数末两位皆为0,为闰年
BREQ COM0 ;返回(二月有29日)
RJMP DAY1 ;否则为3月1日
T30: SUBI R16,7 ;减7调整
BRNE T31 ;$30-$29-7=0
ADIW R26,1 ;指向月
LD R16,X
CPI R16,2
BRNE COM0 ;非2月返回
RJMP DAY1 ;闰年的2月30日为3月1日
T31: DEC R16 ;$31-$29-7=1&$32-$29-7=2
BRNE DAY1 ;日为32 ,为下月1日
ADIW R26,1 ;日为31 ,指向月
LD R16,X
SUBI R16,8 ;月份减去8
BRCC SCHY
INC R16 ;月份小于8,差增1,奇数变偶数
SCHY: SBRS R16,0
RJMP COM0 ;1-7月奇数月为大月/8-12月偶数月为大月;有31日,返回
DAY1: LDI R26,LOW(dtpnt+3)
LDI R27,HIGH(dtpnt+3);指向日
LDI R16,1 ;
RCALL DHM1 ;日置为1,月加1
CPI R16,$13
BRNE COM0
LDI R16,1 ;月变为13,改为1
RCALL DHM1 ;年十个位加1调整,可能有$99+1=$A0
CPI R16,$A0
BRNE COM0 ;
RCALL DHM ;年千百位加1调整
COM0: POP R7
POP R26
POP R27
POP R16
RET
DHM: CLR R16 ;秒,分,时单元清除,高位加1
DHM1: ST X+,R16
DHM3: LD R16,X
INC R16 ;
CPI R16,$0A ;若个位BCD码未变成$0A
BRHS DHM2 ;例如$58+1=$59,不须调整;
SUBI R16,$FA ;否则做减$FA调整:例如$49+1-$FA=$50
DHM2: ST X,R16 ;并将调整结果送回
RET
;范例40
.ORG 0 ;8535UART串行通讯程序,晶振4MHZ
.EQU DTPINT=$180 ;UBRR=12 波特率19200(REL.ERR.=0.16%)
.EQU DRPINT=$1D0
STRT30: RJMP RST30
.ORG $00B
RJMP U_RXC ;UART接收完成中断
.ORG $00C
RJMP U_TXC ;UART发送寄存器空中断
.ORG $011
RST30: LDS R16,$A3 ;
CBR R16,3
STS $A3,R16 ;清完整ASCII数据块接收到标志($A3,1),错误标志(FE/OR)($A3,0)
LDI R16,12
OUT UBRR,R16 ;BAUD RATE=FCP/16(UBRR+1)=19200
LDI R27,HIGH(DIPINT)
MOV R6,R27
LDI R26,LOW(DTPINT)
MOV R7,R26 ;发送数据指针在r6r7(dtpint)
CLR R11
INC R11
LDI R16,$30 ;发送数据块长度为$30
MOV R12,R16
RCALL CRC0 ;得到CRC检测之余式(冲掉$0D&$0A)
INC XL
INC XL
LDI R16,$0D
ST X+,R16
LDI R16,$0A
ST X,R16 ;在数据块末尾加$0D&$0A,实际发送数据块长度为$32
LDI R16,$B8 ;允许UART发送和接收,接收中断,发送寄存器空中断,8位数据
OUT UCR,R16
LDI R16,HIGH(DRPINT)
MOV R8,R16
LDI R16,LOW(DRPINT)
MOV R9,R16 ;r8,r9:接收缓存区指针(FIRST POINT TO $1D0)
CLR R10 ;接收数据块长预先清除
SEI ;
HH30: LDS R16,$A3
SBRC R16,0 ;错误接收?
RJMP RCVER ;错误处理
SBRS R16,1 ;接收数据完成?
RJMP HH30 ;否,转再查询
RCVEF: CLR R11 ;块长予处理
INC R11
DEC R10
DEC R10 ;$0D&$0A不算块长度之内(故将块长减2)
MOV R12,R10 ;(R11,R12):块长
LDI XH,HIGH(DRPINT)
MOV R8,XH
LDI XL,LOW(DRPINT)
MOV R9,XL
RCALL CRC0 ;恢复出CRC余式
LDI R16,$0D
CP R16,R14
BRNE CRCER
LDI R16,$0A
CP R16,R15 ;恢复出$0D$0A为正确接收
BREQ HH30
CRCER: ;. ;循环冗余检测错误处理
;.
;.
RJMP STRT30
RCVER: CBI UCR,RXCIE
;. ;接收错误(FE/OR)处理
;. ;(过程略)
;.
RJMP STRT30
:UART接收数据块程序
U_RXC: PUSH R16
IN R16,SREG
PUSH R16
PUSH R26
PUSH R27
RSC1: IN R16,USR ;UART状态寄存器
ANDI R16,$18 ;FE/OR ERROR?
BRNE RVERR ;错误转
INC R10 ;块长加1
MOV XH,R8
MOV XL,R9 ;r8r9:接收数据指针,首指$1D0
IN R16,UDR
ST X+,r16 ;
MOV R8,XH
MOV R9,XL
CPI R16,$0A ;收到最末字符(回车命令LF)?
BRNE RSCOM
LDS R16,$A3
SBR R16,2 ;建立数据块接收完毕标志
STS $A3,R16
CBI UCR,RXCIE ;禁止接收中断
RJMP RSCOM
RVERR: LDS R16,$A3
SBR R16,1
STS $A3,R16 ;$A3,0:FE/OR错误接收标志
RSCOM: POP R27
POP R26
POP R16
OUT SREG,R16
POP R16
RETI
; UART发送数据块程序
U_TXC: PUSH R16
IN R16,SREG
PUSH R16
PUSH R26
PUSH R27
SPSV1: MOV XH,R6
MOV XL,R7 ;发送数据指针,首指$180
LD R16,X+ ;取发送数据,调指针
MOV R6,XH
MOV R7,XL
SPS11: OUT UDR,R16 ;送入数据寄存器,移入发送移位寄存器后即引起数据寄存器空中断
CPI R16,$0A
BRNE SPCOM
CBI UCR,UDRIE ;发送最末字符后禁止发送寄存器空中断
LDI R16,HIGH(DRPINT)
MOV R8,R16
LDI R16,LOW(DRPINT)
MOV R9,R16 ;接收数据指针初始化,指向$1D0
;CBI USR,6 ;
SPCOM: POP R27
POP R26
POP R16
OUT SREG,R16
POP R16
RETI
.DSEG
.ORG $180
DTPINT:.BYTE $32
;$41,$45,$65,$73,$46,$42,$40,$6F,$33,$44,$66,$8C,$4D,$4B,$2F,$67
;$42,$4F,$66,$78,$47,$45,$44,$63,$32,$48,$60,$7C,$6D,$45,$2A,$63
;$43,$56,$55,$53,$4D,$4F,$40,$2E,$31,$42,$67,$4C,$47,$4A,$38,$39
;$0D,$0A
.EQU DRPINT=$1D0
.ORG $1D0
DRPINT: .BYTE $34 ;(内容略)
;范例41 ;外部中断int0接收ASCII码数据块
.ORG 0 ;8515/8535/晶振4MHZ
STRT31: RJMP RST31
RJMP EX_INT0
.ORG $00D ;8535外部中断0
RST31: LDI R17,HIGH(ramend)
OUT SPH,R17
LDI R17,LOW(ramend)
OUT SPL R17
LDI R17,2
OUT TCCR1B,R17 ;4mhz/8分频,计数单位为2微秒,TCCR1B:$2e
LDI R17,$40
OUT GIMSK,R17 ;gimsk,6(允许int0中断)
LDI R17,2
OUT MCUCR,R17 ;设INT0为下降沿中断(mcucr'b1&b0=10)
CBI DDRD,2 ;int0 为输入
;.
;. ;其他初始化略
SEI ;
CLRBUF: LDI R27,1
CLR R26 ;接收数据缓存区首址$100
LDI R17,$40
OUT GIMSK,R17 ;gimsk,6
CLR R17
CLRLOP: ST X+,R17
CPI R26,$48
BRNE CLRLOP ;清接收缓存区($100--$147)
LDS R16,$A3
CBR R16,$60
STS $A3,R16 ;接收错误($A3,6)和接收完成($A3,5)标志清除
CLR5: ;.
;. ;背景程序略
RJMP CLR5 ;
RCVST: CBI DDRD,2 ;int0 为输入
SER R16 ;接收开始
OUT PORTC,R16 ;关显
LDI R27,1
CLR R26 ;接收数据指针,首指$100
LDI R17,18 ;接收18个字符,其末尾为$0D$0A
MOV R14,R17
RCALL RVBYT1 ;接收第一个字符
RJMP RVBYT
RVBLOP: RCALL RVBYT2 ;接收第二个字符及其后字符
RVBYT: LDS R17,$A3
SBRC R17,6
RJMP CLRBUF ;接收出错,转去清除$100--$14F
SBRC R17,5
RJMP DTCOM ;接收完整数据块,转去处理
ST X+,R16
DEC R14
BRNE RVBLOP ;未收完18个字符,继续
CPI R26,$42 ;指针达到$142?
BREQ DTCOM ;接收完整数据块,转去处理
DEC R26
DEC R26 ;$0D$0A(CR&LF)丢掉
RCVLP: LDI R17,18
MOV R14,R17
RJMP RVBLOP
DTCOM: LDI R27,1
CLR R26 ;接收数据首地址:$100
DLLOP: CLR R29
LDI R28,$90 ;处理ASCII码程序acum要求将数据放在$90--$9f
LD R16,X
CPI R16,$50 ;第一个字符约定为‘P’才有效
BRNE RVCOM1 ;也是判断处理结束符
DLLOP1: LD R16,X+
ST Y+,R16
CPI R28,$A0
BRNE DLLOP1 ;传16个字符
PUSH R26
PUSH R27
RCALL ACUM ;ASCII变BCD再变为二进制数,累加
POP R27
POP R26
BRTS RVCOM1 ;ASCII码无效,转出!
RJMP DLLOP
RVCOM1:
CLT
RJMP CLRBUF ;转去清缓存区,重新接收
;晶振采用4MHZ,指令(DEC+BRNE)耗时0.75微秒)!
EX_INT0:POP R16 ;int0中断服务子程序
POP R16 ;废弃返回地址
LDI R16,HIGH(RCVST)
PUSH R16
LDI R16,LOW(RCVST)
PUSH R16 ;设置返回地址
IN R16,GIMSK ;禁止int0中断
CBR R16,$40
OUT GIMSK,R16
RETI
RVBYT1: LDI R17,2 ;查到0接收时,再做一次接收
MOV R15,R17
LDI R17,50 ;第一个起始位半位延时(50*0.75=38微秒)
MOV R12,R17
RJMP RVBCM
RVBYT2: LDI R17,2
MOV R15,R17
RVBY2: LDI R17,147 ;110微秒>1位宽/9600baud,110/0.75=147
MOV R12,R17
TEST3: SBI PORTD,2
SBIS PIND,2 ;停止位超宽测试
RJMP RVST
DEC R12
BRNE TEST3
LDS R16,$A3 ;110微秒内查到低电平为起始位
ORI R16,$20
STS $A3,R16 ;否则为接收结束,令$A3,5=1
RET
RVST: LDI R17,60 ;60*0.75=45微秒(半位延时)
MOV R12,R17
RVBCM: DEC R12
BRNE RVBCM
LDI R17,9 ;1位起始+8位数据
MOV R13,R17
SBI PORTD,2
SBIC PIND,2
RJMP RVER1 ;无效起始位(半位测试)
RVLOP: LDI R17,130 ;may be 128-132/位延时常数
MOV R12,R17
RVLP1: DEC R12 ;0.25微秒
BRNE RVLP1 ;0.5微秒/if condition is true
SEC
SBI PORTD,2
SBIS PIND,2
CLC
DEC R13
BRNE OVRRC ;不是停止位,转数据位接收
BRCC RVER1 ;无效停止位,出错
TST R16 ;
BRNE RBYRT ;不为0,收到一个有效字符
DEC R15
BRNE RVBY2 ;2次接收到$00,出错
RVER1: LDS R16,$A3
ORI R16,$40 ;接收出错标志
CBR R16,$20
STS $A3,R16
RBYRT: RET
OVRRC: ROR R16 ;组织数据
RJMP RVLOP ;100.7微秒/程序实设位宽
;范例42 ;8535'T0中断发送ASCII码程序,晶振4MHZ
.EQU DATA2=$150
.ORG $000
STRT32: RJMP RST32
.ORG 009
RJMP T0_OVF
.ORG $011
RST32: SER R17
OUT DDRB,R17 ;B口为输出
OUT PORTB,R17 ;输出高电平
LDI R16,2 ;0B00000010/8 DIVIDED(4fc/8:2微秒)
OUT $33,R17 ;写入tccr0
LDI R16,204 ;(256-204)*2=104微秒/9600baud 104微秒/位!
OUT TCNT0,R17 ;
LDI R17,HIGH(ramend)
OUT SPH,R17
LDI R17,LOW(ramend)
OUT SPL,R17
LDI R25,HIGH(DATA2)
LDI R24,LOW(DATA2);发送数据指针
LDS R17,$A3
CBR R17,$14 ;发送出错标志($A3,4)/发送完毕标志位($A3,2)清除!
STS $A3,R17
SEI
LDI R17,1
OUT TIMSK,R17 ;允许T/C0溢出中断
CLR R17 ;位计数器请除
HH32: LDS R16,$A3
SBRC R16,4
RJMP HHER32 ;出错
SBRS R16,2 ;
RJMP HH32 ;查询等待数据块发送完成
;. ;其他程序略
;. ;可安排接收对方发来数据程序,见STRT33
RJMP RST32
HHER32:;.
;. ;错误处理略
RJMP RST32
T0_OVF: PUSH R16
IN R16,SREG
PUSH R16
PUSH R26
PUSH R27
IN R16,TCNT0
INC R16
SUBI R16,52 ;重写入一位定时常数(带修正)
OUT TCNT0,R16
MOV R26,R24 ;数据指针
MOV R27,R25
CPI R17,10
BREQ SND10
TST R17
BRNE SND9
SND0: CBI PORTB,0 ;发起始位(0)
RJMP SVCOM
SND9: CPI R17,9
BRNE SND18 ;1-8为数据位
SBI PORTB,0 ;9为停止位(1)
CLR R17 ;停止位发完后,位计数器清除
ADIW R24,1 ;指针增1,指下一位数据
LD R16,X
CPI R16,$0A ;本次发送的是$0A?
BRNE SVCOM1
LDI R17,10 ;停止位标志
RJMP SVCOM1
SND10: LDS R16,$A3
SBR R16,4 ;发送完成标志
STS $A3,R16 ;
SND11: CLR R16
OUT TCCR0,R16 ;关闭T/C0
CLR R17 ;清位计数器
LDI R24,LOW(DATA2);发送指针初始化
LDI R25,HIGH(DATA2)
RJMP SVCOM1
SENDER: LDS R16,$A3
SBR R16,$10
STS $A3,R16 ;建出错标志
RJMP SND11
SND18: BRCC SENDER ;大于10为错误
LD R16,X
ROR R16 ;发送位传到进位C
BRCC S182
SBI PORTB,0 ;C(=1)-->PB0($18,0)
BRCS S183
S182: CBI PORTB,0 ;C(=0)-->PB0($18,0)
S183: LD R16,X
ROR R16 ;
ST X,R16 ;保存剩余位
MOV R24,R26 ;存数据指针
MOV R25,R27
SVCOM: INC R17 ;位计数器增1
SVCOM1: POP R27
POP R26
POP R16
OUT SREG,R16
POP R16 ;恢复现场
RETI
;范例43 ;8515/8535/晶振4MHZ RECEIVING ASCII CHAR. BY TCNT0&PB0
.EQU DATA3=$100 ;UES R11 SAVE SREG, R12 R13:数据指针DATA3
;R14: 块长(BLOCK LENGTH) ,R15:接收字符暂存寄存器
.ORG 0 ;R16:(THE BIT SEQUENCE COUNTER)位序列计数器
;R17:WORKING REG.R18:FLAG UNIT, BAUD RATE:9600
STRT33: RJMP RST33 ;X&Y:POINTER/接收数据缓存区首地址:$100
.ORG $009 ;$007(8515)
RJMP T0_OVF1
.ORG $011 ;$00D(8515)
RST33: LDI R17,HIGH(ramend)
OUT SPH,R17
LDI R17,LOW(ramend)
OUT SPL,R17
LDI R17,HIGH(DATA3)
MOV R12,R17
LDI R17,LOW(DATA3)
MOV R13,R17 ;R12R13:接收数据指针
CLRBF1: CLR R16
CLRLP: ST X+,R16
CPI R26,$48
BRNE CLRLP ;接收数据缓存区请除
CLR R18 ;标志寄存器请除/R18,2:完整数据块收到,R18,1
;第一字符(块长)收到:R18,0:出错
LDI R17,$02 ;8535:$01
OUT TIMSK,R17 ;允许T/C0溢出中断
LDI R17,6 ;外部脉冲下降沿计数
OUT TCCR0,R17
CBI DDRB,0 ;PB0为输入
LDI R17,$FF
OUT TCNT0,R17 ;计一个数即中断
;
SEI ;
TEST1: RCALL DSPLY3 ;调串行移位显示子程序
SBRC R18,0 ;
RJMP DLERR ;出错,转错误处理
SBRS R18,2 ;数据块接收完成?
RJMP TEST1
LDI R16,128
DECLP: DEC R16
BRNE DECLP
RJMP DTCOM0 ;先延时,再转处理数据块
DLERR: ;. ;出错处理
;.
;.
RCALL DL50 ;延时50毫秒后
RJMP RST33 ;重新接收
DTCOM0: LDI R27,1
CLR R26 ;数据存储区首地址$100
DLLOP0: CLR R29
LDI R28,$90 ;ASCII码处理区为$90--$9f
LD R16,X
CPI R16,$50 ;字母P打头才有效
BRNE RVCOM0 ;否则为无效字串或ASCII码处理结束
DLLO1: LD R16,X+
ST Y+,R16
CPI R28,$A0
BRNE DLLO1 ;传送16个字符
PUSH R26
PUSH R27
RCALL ACUM ;处理一组ASCII码数据
POP R27
POP R26
BRTC DLLOP0 ;T=1,ASCII码数据无效
CLT
;. ;错误处理
RJMP STRT33
RVCOM0:;. ;错误处理
;.
RJMP STRT33
T0_OVF1:IN R11,SREG ;T/C0中断服务
PUSH R17
CPI R16,0 ;起始位下降沿中断?
BRNE T0SV10
LDI R17,2 ;YES
OUT TCCR0,R17 ;改为内定时(4MHZ/8分频)
LDI R17,232 ;半位时间常数24 定48微秒(<52)
OUT TCNT0,R17
RJMP T0SV6
T0SV10: CPI R16,1 ;1,半位定时到
BRNE T0SV2
SBI PORTB,0
SBIC PINB,0
RJMP T0ERR ;高电平,错误
RJMP T0SV60 ;低电平,有效起始位
T0SV2: CPI R16,10 ;
BRNE T0SV3
SBI PORTB0 ;10,接收停止位
SBIS PINB,0
RJMP T0ERR ;低电平,错误
LDI R17,6
OUT TCCR0,R17 ;改为外部脉冲下降沿计数,为接受下一位字符准备
LDI R17,$FF ;计一个数即中断
OUT TCNT0,R17
CLR R16 ;位计数器请除
SBRC R18,1 ;是第一个字符(r18,1=0)?
RJMP T0SV21 ;否,为块内数据
MOV R14,R15 ;块长转入r14
SBR R18,2 ;块长已收到
RJMP T0SV61
T0SV21: PUSH XL
PUSH XH
MOV XH,R12
MOV XL,R13 ;取缓存区指针
ST X+,R15 ;字符送入缓存区
MOV R12,XH
MOV R13,XL
POP XH
POP XL
DEC R14
BRNE T0SV61
SBR R18,4 ;块长减为0,完整数据块收到
CLR R16
OUT TCCR0,R16 ;停止TCNT0
RJMP T0SV61
T0SV3: BRCC T0ERR ;出错(大于10)
CLC ;2--9:数据位
SBI PORTB,0 ;接收一位数据
SBIC PINB,0
SEC
ROR R15 ;数据组织到R15
T0SV60: IN R17,TCNT0 ;读TCNT0计数值
INC R17 ;
SUBI R17,52
OUT TCNT0,R17 ;写入补偿后的时间常数
T0SV6: INC R16 ;位序列计数器增1
T0SV61: POP R17
OUT SREG,R11
RETI
T0ERR: CLR R16
T0ERL: SBR R18,1 ;错误接收标志
OUT TCCR0,R16 ;停止TCNT0
RJMP T0SV61
;范例44
.ORG 0 ;8535多机通讯主机程序/振4MHZ
.EQU DTPINT=$180 ;UBRR=12,波特率19200(REL.ERR.=0.16%)
.EQU DRPINT=$1C0 ;主机发往#1,#2,#3,#分机数据在
;$180-18F,$190-19F,$1A0-1AF和$1B0-1BF
STRT34: RJMP RST34 ;主机接收#1,#2,#3,#4分机之数据块分别在
;$1C0-1CF,$1D0-1DF,$1E0-1EF和$1F0-1FF
.ORG $00B
RJMP U_RXC ;UART接收完成中断
.ORG $00C
RJMP U_TXC ;UART 发送完成中断
.ORG $011
RST34: LDI R16,12
OUT UBRR,R16 ;BAUD RATE=FCP/16(UBRR+1)=4000000/(16*13)=19200
CLR R15 ;分机号初始化
LDI R27,HIGH(DTPINT)
LDI R26,LOW(DTPINT);发送数据指针,首指$180
LDI R29,HIGH(DRPINT)
LDI R28,LOW(DRPINT);接收数据指针(POINT TO $1C0)
NEXTNO: LDI R16,$18
OUT UCR,R16 ;允许UART接收和发送,8位数据模式
INC R15 ;指向分机
OUTLP: OUT UDR,R15 ;呼分机号
TSLOP: IN R16,USR
SBRS R16,7
RJMP TSLOP ;分机返回机号?
IN R16,UDR
CP R16,R15 ;与发送分机号符合?
BRNE OUTLP ;不符再发
TXLOP: LD R16,X+
OUT UDR,R16 ;向分机发送数据块
TESTL: IN R17,USR
SBRS R17,5 ;发送寄存器空?
RJMP TESTL
CPI R16,$0A
BRNE TXLOP ;发完整个数据块?
RXTST: IN R17,USR
SBRS R17,7 ;RXC=1 分机发来数据
RJMP RXTST
IN R16,UDR
ST Y+,R16 ;接收数据转入内存
CPI R16,$0A
BRNE RXTST ;接收完整数据块后
MOV R16,R15
CPI R16,4 ;转与下一分机通讯(只有4台分机)
BRNE NEXTNO ;直到轮询完毕
HH34: RJMP HH34 ;可改为处理分机发来数据,再转入下一周轮询
.DSEG
.ORG $180
DTPINT:.BYTE $40
$41 $45 $65 $73 $46 $42 $40 $6F $33 $44 $66 $5C $4D $4B $0D $0A
$42 $4F $66 $78 $47 $45 $44 $63 $32 $48 $60 $7C $6D $45 $0D $0A
$43 $56 $55 $53 $4D $4F $40 $2E $31 $42 $67 $4C $47 $4A $0D $0A
$45 $54 $59 $63 $3D $4B $48 $2F $35 $48 $69 $3C $77 $43 $0D $0A
.ORG $1C0
DRPINT: .BYTE $40
;范例45
.ORG 0 ;8535多机通讯1#分机程序,晶振4MHZ
.EQU DTPIT1=$180 ;UBRR=12 波特率19200(REL.ERR.=0.16%)
.EQU DRPNT1=$1C0
STRT35: RJMP RST35
.ORG $00B
RJMP UARXC ;UART接收完成中断
.ORG $00C
RJMP UATXC ;UART发送寄存器空中断
.ORG $011
RST35: CLR R18 ;请除主机发来完整数据块标志(R18,7)/主机呼号选中分机
;标志(R18,6)
LDI R16,12
OUT UBRR,R16 ;[BAUD RATE=FCP/16(UBRR+1)]
LDI R16,HIGH(DRPNT1)
MOV R8,R16
LDI R16,LOW(DRPNT1)
mov R9,R16 ;r8,r9:接收数据指针(FIRST POINT TO $1C0)
LDI R16,$98 ;允许UART发送,接收,接收完成中断
OUT UCR,R16
SEI
CLR R15
INC R15 ;1#分机设为1/2#分机设为2/3#分机设为3/4#分机设为4
RXDTS: SBRS R18,6 ;收到主机发来呼号?
RJMP RXDTS
OUT UDR,R15 ;将分机号反还主机
TXDON: IN R16,USR
SBRS R16,5
RJMP TXDON ;发送寄存器空?
RCVBLK: SBRS R18,7
RJMP RCVBLK ;等待接收主机发来数据块
LDI R16,HIGH(DTPIT1)
MOV R6,R16
LDI R16,LOW(DTPIT1)
MOV R7,R16 ;设置发送数据指针r6r7,首指$180
SBI UCR,5 ;UDRIE=1 ,引起发送寄存器空中断
TXDN: SBIC UCR,5
RJMP TXDN
RJMP RST35 ;等待UDRIE=0 ,向主机发送数据块完毕后,转下一轮通讯
:UART中断接收程序
UARXC: IN R14,SREG
TST R18
BREQ NUMB ;无主机呼号选中标志,查机号
PUSH R26
PUSH R27
IN R17,UDR ;读入接收数据
MOV XH,R8
MOV XL,R9 ;r8r9:接收数据缓存区指针,首指$1C0
ST X+,R17
MOV R8,XH
MOV R9,XL
CPI R17,$0A ;收到换行符?
BRNE RSCOM1
SBR R18,$80 ;建立数据块接收完毕标志
RSCOM1: POP R27
POP R26
DRETI: OUT SREG,R14
RETI
NUMB: IN R17,UDR
CP R17,R15 ;主机呼号与本分机号符合?
BRNE DRETI ;不符,转
SBR R18,$40 ;建选中标志
RJMP DRETI
; UART中断发送数据程序
UATXC: IN R16,SREG ;r6 r7:the sendDATA pointer(FIRST POINT TO $180)
PUSH R16
PUSH R26
PUSH R27
MOV XH,R6
MOV XL,R7 ;发送数据指针
LD R16,X+
MOV R6,XH
MOV R7,XL
OUT UDR,R16 ;发送数据写入数据寄存器
CPI R16,$0A ;发送LF?
BRNE SDCOM
CBI UCR,5 ;禁止数据寄存器空中断(清UDRIE)
LDI R16,HIGH(DRPINT)
MOV R8,R16
LDI R16,LOW(DRPINT)
MOV R9,R16 ;为接收作准备(FIRST POINT TO $1C0)
SDCOM: POP R27
POP R26
POP R16
OUT SREG,R16
POP R16
RETI
.DSEG
.ORG $180
DTPIT1: .BYTE $10
;$41 $45 $65 $73 $46 $42 $40 $6F $33 $44 $66 $5C $4D $4B $0D $0A
.ORG $1C0
DRPNT1: .BYTE $10
;$41 $45 $65 $73 $46 $42 $40 $6F $33 $44 $66 $5C $4D $4B $0D $0A
;范例46
.ORG $000 ;RS232<->RS485通讯标准转换/晶振4MHZ
STRT36: RJMP RST485 ;使用8515!
.ORG $009
RJMP U_RXC ;UART 接收中断
.ORG $00D
RST485: LDI R16,2
OUT SPH,R16
LDI R16,$5f ;
OUT SPL,R16
LDI R16,$98 ;允许UART接收和发送,允许接收中断
OUT UCR,R16
LDI R16,12
OUT UBRR,R16 ;波特率19200
SBI DDRB,7
SBI DDRB,6 ;PB7,PB6为输出
CBI PORTB,7
CBI PORTB,6 ;PB7控制485发送(高有效)PB6控制485接收(低有效)
SEI
HERE0: CPI R16,3 ;收到停止符?
BRNE HERE0 ;未收到循环等待
HERE1: SBIS USR,6 ;
RJMP HERE1 ;等待停止符发送完毕
CBI PORTB,7 ;禁止485发送
CBI PORTB,6 ;允许485接收
SBI USR,6 ;写‘1’清除发送完成标志!
CLR R16
RJMP HERE0 ;转等待下一轮中转
U_RXC: SBI PORTB,7 ;允许485发送
SBI PORTB,6 ;禁止485接收,
IN R16,UDR ;读出接收数据,同时清除接收中断标志
TSAGN: SBIS USR,6 ;上一数据发送完毕?
RJMP TSAGN
SBI USR,6 ;清除发送完成标志
OUT UDR,R16 ;转发本次接收数据
RETI
;范例47
.EQU DATA4=$220
.ORG $000 ;同步串口通讯主机程序,晶振4MHZ
STRT37: RJMP RST37
.ORG $00A ;8535 SPI中断矢量(8515为$008)
RJMP SPINT
.ORG $011 ;$00D(8515)
RST37: LDI R16,2
OUT SPH,R16
LDI R16,$5f
OUT SPL,R16 ;堆栈指针初始化
LDI R16,$A0
OUT DDRB,R16 ;SCK,MOSI为输出
LDI R16,$DC
OUT SPCR,R16 ;允许SPI中断,先发送高位,主控方式,时钟为主频4分
;频,后沿有效
LDI XH,HIGH(DATA4)
LDI XL,LOW(DATA4);数据指针
LDI R16,$30
MOV R15,R16 ;数据块长
LDI R16,12 ;0.25微秒
SPI0: DEC R16 ;0.25微秒
BRNE SPI0 ;0.5微秒 总延时9微秒
LD R16,X
OUT SPDR,R16 ;写发送数据寄存器,启动发送
SEI
HH37: RJMP HH37 ;背景程序略
SPINT: IN R14,SREG
IN R16,SPDR ;读出接收数据
ST X+,R16
DEC R15
BRNE SPI1 ;数据收发完毕?
OUT SPCR,R15 ;是,停止收发
OUT SREG,R14
RETI
SPI1: LDI R16,6 ;0.25微秒
SPI1A: DEC R16 ;0.25微秒
BRNE SPI1A ;0.5微秒 总共4.5微秒
LD R16,X
OUT SPDR,R16 ;发下一个数据
OUT SREG,R14
RETI
;范例48
.ORG $000
STRT37S:RJMP RST37S ;同步串口通讯从机程序(8515) 晶振4MHZ
.ORG $008 ;$00A(8535)
RJMP SPINTS ;同步串口中断矢量
.ORG $00D ;$011(8535)
RST37S: LDI R16,2
OUT SPH,R16
LDI R16,$5f
OUT SPL,R16
LDI R16,$40
OUT DDRB,R16 ;MISO为输出
LDI R16,$CC
OUT SPCR,R16 ;允许SPI中断,先发送高位,从控方式,时钟为主频4分频,后沿有效
LDI YH,HIGH(DATA4)
LDI YL,LOW(DATA4);数据指针
LDI R16,$30 ;数据长度
LD R15,Y
OUT SPDR,R15 ;写入数据寄存器
SEI
HH37S: RJMP HH37S ;背景程序从略
SPINTS: IN R14,SREG
IN R15,SPDR ;读接收数据
ST Y+,R15
DEC R16
BRNE SPI2 ;数据块收发完毕
OUT SPCR,R16 ;停止中断收发
RJMP SPI3
SPI2: LD R15,Y
OUT SPDR,R15 ;发下一数据
SPI3: OUT SREG,R14
RETI
;范例49 以模拟串口与串行移位寄存器74165通讯,以74165驱动LED显示子程序
DSPLY3: SBI DDRC,1 ;PC1,串行数据输出
SBI DDRC,0 ;PC0,移位时钟
CBI PORTC,0 ;
LDI R17,8 ;8字节显示缓存区$60(高)--$67(低))
MOV R8,R17
CLR XH
LDI XL,$60 ;指针,首指最高位($60)
SRDLOP: LDI R17,8 ;8位/字节
MOV R9,R17
LD R10,X+
LDI ZH,HIGH(TABLE*2)
LDI ZL,LOW(TABLE*2);使用DSPY子程序段选表
ADD ZL,R10 ;加代码寻址
BRCC DSPL1
INC ZH
DSPL1: LPM ;取段选码
COM R0 ;取为反码
SENDLP: ROR R0 ;段选码右移一位 C<--R0最低位
CBI PORTC,1
BRCC SNDL1 ;进位C传给PC1
SBI PORTC,1
SNDL1: SBI PORTC,0 ;移位时钟,上升沿有效
CBI PORTC,0 ;移位时钟变低
DEC R9
BRNE SENDLP ;8位段选码循环右移
DEC R8
BRNE SRDLOP ;8位LED显示数据都更新一遍?
RET ;是,结束
;脉宽调制(PWM)输出程序
;范例50 ;以定时器定时产生精确半秒信号,以PD5输出精确秒号
.ORG $000 ;晶体实测频率为8000367HZ
STRT40: RJMP RST40 ;USE 8535
.ORG $008 ;t/C1 overflow vector
RJMP T1_OVF
.ORG $011
RST40: LDI R16,HIGH(ramend)
OUT SPH,R16
LDI R16,LOW(ramend)
OUT SPL,R16
SBI DDRD,5 ;PD5(OC1A)为输出
CBI PORTD,5 ;初始输出为低
LDI R16,2 ;8分频 INT(8000367/8)=1000046) 折半500023定半秒
OUT TCCR1B,R16 ;T/C1控制寄存器(I/O ADDR:$2E)
LDI R16,$5E ;500023=65536*8-24265=$10000*8-$5EC9
OUT TCNT1H,R16 ;TCC=$5EC9 先写高位字节
LDI R16,$C9
OUT TCNTIL,R16
LDI R16,$04
OUT TIMSK,R16 ;允许T/C1溢出中断/8535C/t1:timsk,2&t0:timsk,0
LDI R17,8 ;8次中断定半秒
CLR R16
OUT TIFR,R16 ;清除定时/计数器中断标志
SEI ;
HH40: RJMP HH40 ;背景程序略
T1_OVF: IN R5,SREG ;保存状态寄存器
DEC R17
BRNE COMP1 ;定时时间到?
IN R16,PORTD ;读入PD5当前状态
LDI R17,$20
EOR R16,R17 ;求反PD5(OC1A)输出
OUT PORTD,R16
IN R17,TCNT1L ;*
IN R16,TCNT1H ;*读回TCNT1计数值
SUBI R17,$37 ;*
SBCI R16,$A1 ;*减去$5EC9之补码$A137
SUBI R17,$FF ;*补偿指令8条占一个计数单位
SBCI R16,$FF ;*补偿后TCC=$5EC9+(TCNT1)+1
OUT TCNT1H,R16 ;*
OUT TCNT1L,R17 ;*写入TCNT1
LDI R17,8 ;重新写入中断次数
COMP1: OUT SREG,R5
RETI
;范例51 ;以比较匹配A达到时交替输出高低电平及写入其维持
;时间常数之方法实现脉宽调制输出
.ORG $000
STRT41: RJMP RST41 ;5.008MS(高):10.000MS(低) 晶振4MHZ
.ORG $006
RJMP T1_CMPA ;USE 8535
.ORG $011
RST41: LDI R16,HIGH(RAMEND)
OUT SPH,R16
LDI R16,LOW(RAMEND)
OUT SPL,R16
LDI R16,$80 ;T/C1比较匹配A达到时,清除输出脚oc1a
OUT TCCR1A,R16
LDI R16,$0B ;64分频 ctc1=1 比较匹配达到清tcnt1
OUT TCCR1B,R16
SBI DDRD,5
SBI PORTD,5 ;pd5(oc1a)初始化输出为高
CLR R16
OUT TCNT1H,R16 ;予清除tcnt1
OUT TCNT1L,R16
LDI R16,1
OUT OCR1AH,R16
LDI R16,$39 ;写比较匹配寄存器(313*0.25*64=5.008MS)
OUT OCR1AL,R16
LDI R16,$10
OUT TIMSK,R16 ;允许比较匹配A中断
SEI
HH41: RJMP HH41 ;背景程序略
T1_CMPA:IN R5,SREG
IN R16,TCCR1A
SBRS R16,6
RJMP OUTLOW ;当前输出低电平,转
LDI R16,1
OUT OCR1AH,R16
LDI R16,$39 ;写入高电平维持时间313
OUT OCR1AL,R16
LDI R16,$80 ;比较匹配A达到时,OC1A输出为低
OUT TCCR1A,R16
OUT SREG,R5
RETI
OUTLOW: LDI R16,2
OUT OCR1AH,R16
LDI R16,$71 ;写入低电平维持时间625(=$271) (625*0.25*64=10.000MS)
OUT OCR1AL,R16
LDI R16,$C0 ;比较匹配A达到时,OC1A输出为高
OUT TCCR1A,R16
OUT SREG,R5
RETI
;范例52 ;以比较匹配达到时求反输出并按高低电平写入
.ORG $000 ;维持时间之方法实现脉宽调制输出
STRT42: RJMP RST42 ;5.008MS(高):10.000MS(低) 晶振4MHZ
.ORG $006
RJMP T1_CMPA
.ORG $011
RST42: LDI R16,HIGH(ramend)
OUT SPH,R16
LDI R16,LOW(RAMEND)
OUT SPL,R16
LDI R16,$40 ;比较匹配A达到时,对OC1A输出求反
OUT TCCR1A,R16
LDI R16,$0C ;256分频 ctc1=1 比较匹配达到时 清除cnt1
OUT TCCR1B,R16
SBI DDRD,5 ;PD5(oc1a)为输出
SBI PORTD,5 ;初始输出为高
CLR R16
OUT TCNT1H,R16 ;清除tcnt1
OUT TCNT1L,R16
OUT OCR1AH,R16
LDI R16,78 ;高电平时间常数78
OUT OCR1AL,R16
LDI R16,$10
OUT TIMSK,R16 ;允许比较匹配A中断
SEI
HH42: RJMP HH42 ;背景程序略
T1_CMPA:IN R5,SREG ;
IN R16,PORTD
SBRC R16,5
RJMP T1CM1 ;当前oc1a为高,转
LDI R16,0
OUT OCR1AH,R16
LDI R16,156 ;低电平时间常数156
OUT OCR1AL,R16
OUT SREG,R5
RETI
T1CM1: LDI R16,0
OUT OCR1AH,R16
LDI R16,78 ;高电平时间常数78
OUT OCR1AL,R16
OUT SREG,R5
RETI
;模/数转换和数/模转换及脉宽调制输出应用
;范例53 ;模拟量采集和3路脉宽调制输出(OCR1A/OCR1B&OCR2)综合程
;序/晶振4MHZ
.ORG $000
STRT50: RJMP RST50 ;avr is AT90S8535
.ORG $00E
RJMP ADCOM ;模数转换完成中断
.ORG $011
RST50: LDI R16,HIGH(ramend)
OUT SPH,R16
LDI R16,LOW(ramend)
OUT SPL,R16 ;堆栈指针初始化
CLR R11 ;通道号初始化
CLR R12
CLR R13 ;累加和予清除
OUT $07,R11 ;ADC通道初始化,指向0#通道
LDI R16,$6C ;T/C2为自运行pwm输出,加法计数匹配清除OC2,减法计
;数匹配置位OC2(正向PWM);对晶振64分频
OUT TCCR2,R16 ;tccr2' ADDR.:$25
LDI R16,$ED ;使能,启动ADC/自由运行/转换完成中断/对晶振32分频
OUT ADCSR,R16 ;ADDR:$06 adc控制状态寄存器
IN R16,ASSR
CBR R16,8
OUT ASSR,R16 ;TCNT2 用主时钟!
INC R11
OUT $07,R11 ;予切换到1号ADC通道
SBI DDRD,4
SBI PORTB,4 ;pd4:oc1b
SBI DDRD,5 ;pd5:oc1a pd4,pd5 皆为输出 oc1b初始输出为高
SBI DDRD,7 ;oc2 输出
LDI R16,$E3 ;0B11100011,自运行PWM,COM1A1/0=11,COM1B1/0=10
OUT TCCR1A,R16 ;减法计数匹配清除OC1A,加法计数匹配置位OC1A(反向PWM);加法计
;数匹配清除OC1B,减法计数匹配置位OC1B(正向PWM)
LDI R16,2
OUT TCCR1B,R16 ;tcnt1 8分频
LDI R16,0
OUT TCNT1H,R16 ;wr.high B at first
OUT TCNT1L,R16 ;清除TCNT1
OUT TCNT2,R16 ;清除TCNT2
OUT DDRA,R16 ;A口输入
OUT PORTA,R16 ;输入为高阻态
SEI
COMLP: CPI R11,1
BREQ COMLP ;通道号初始为1,等待切换过去
COML0: CPI R11,1
BRNE COML0 ;通道号再次为1时,0#通道正在转换,7#通道已转换完毕,
;已得到8个A/D采样累加和
ASR R12
ROR R13
ASR R12
ROR R13
ASR R12
ROR R13 ;累加和除以8
BRCC COML1
CLR R16
ADC R13,R16
ADC R12,R16 ;四舍五入
COML1: OUT OCR1AH,R12
OUT OCR1AL,R13
OUT OCR1BH,R12
OUT OCR1BL,R13 ;10位数据写入比较匹配寄存器
ASR R12
ROR R13
ASR R12
ROR R13
BRCC COML2
INC R13
BRNE COML2
DEC R13
COML2: OUT OCR2,R13 ;8位数据写入比较匹配寄存器
CLR R12
CLR R13 ;累加和清除
RJMP COMLP
ADCOM: IN R15,ADCL ;ADC完成中断
IN R14,ADCH
ADD R13,R15 ;模拟数值加入累加和
ADC R12,R14
INC R11
SBRC R11,3
CLR R11 ;total 8 chanales!&8 CHANGED TO 0
OUT $07,R11 ;$07:admux'address REGISTER
RETI
;范例54 ;以R-2R电阻网络和C口配合组成DAC与输入模拟量比较实现模数转换
.ORG $000 ;电阻网络DAC最大输出(AIN0)只能达到3.32V(PCi输出只能达到5V)
STRT51: RJMP RST51 ;输入模拟量最大为4.98V,故应将DAC输出放大1.5倍再与前者比较
.ORG $011 ;也可将输入模拟量衰减为2/3再与DAC输出比较
RST51: LDI R16,2 ;但应将转换结果乘以1.5以使其复原,程序取后者
OUT SPH,R16 ;堆栈指针初始化
LDI R16,$5F
OUT SPL,R16
SER R16
OUT DDRC,R16 ;C口全部为输出,DAC输出为AIN0输入
CLR R16
OUT DDRB,R16 ;B口为输入
LDI R16,$F3
OUT PORTB,R16 ;PB2(AIN0),PB3(AIN1)输入为高阻状态
CLR R15 ;模数转换结果予清除
LDI R16,$80 ;逼近增量初始值
CMPLP: ADD R15,R16 ;模数转换阶段值加逼近增量
OUT PORTC,R15 ;转成模拟量
NOP
NOP
NOP ;4MHZ/等待1微秒
SBIC ACSR,ACO ;输入模拟量大于DAC模拟量,清除ACO
SUB R15,R16 ;否则去掉逼近增量
LSR R16 ;逼近增量折半
BRNE CMPLP ;逼近增量变为0?
MOV R16,R15 ;*是,转换结束
LSR R15 ;*
ADC R15,R16 ;*将转换结果乘以1.5
HH50: RJMP HH50 ;背景程序略
;范例55 ;40点平均在r18r19,累加和在r5r6r7;20点平均在R14R15,累加和在R1R3R4
SLPAV: PUSH R26 ;采样在R8R9,采样数据存储区$150--$19F/工作寄存器r1--r19&r26 r27
PUSH R27
LDI R27,1
LDS R26,$14F ;数据存储区首地址$14F
ADD R7,R9
ADC R6,R8 ;采样加入40点平均累加和
BRCC SLP1
INC R5 ;有进位,高位字节增1
SLP1: ADD R4,R9
ADC R3,R8 ;采样加入20点平均累加和
BRCC SLP2
INC R1 ;有进位,高位字节增1
SLP2: LD R16,X
ST X+,R9
MOV R9,R16 ;置换出最旧采样低位字节
LD R16,X
ST X+,R8
MOV R8,R16 ;置换出最旧采样高位字节
CPI R26,$A0
BRNE SLPA1
LDI R26,$50 ;采样放满存储区后,指针初始化($1A0=$150)
STS $14F,R26
LDS R16,$A4
SBRC R16,4
RJMP SLPA2 ;40点平均时间达到,转
SBR R16,$10 ;设置40点平均时间达到标志
STS $A4,R16
RJMP SLDIV ;转去计算40点平均
SLPA1: STS $14F,R26 ;暂存指针
LDS R16,$A4
SBRS R16,4
RJMP SLPB0 ;还未到40点平均,转
SLPA2: SUB R7,R9
SBC R6,R8 ;到40点平均后除加上新采样外,还要减去最旧采样
BRCC SLDIV
DEC R5 ;不够减,高位字节减1
SLDIV: CLR R12
LDI R16,40
MOV R11,R16
CLR R10
MOV R13,R5
MOV R14,R6
MOV R15,R7
RCALL DIV165 ;计算40点平均
MOV R18,R14
MOV R19,R15 ;存入r18r19
SLPB0: CPI R26,$78
BRNE SLPB1
LDS R16,$A4
SBRC R16,3
RJMP SLPB2
SBR R16,8 ;建20点平均时间到标志
STS $A4,R16
RJMP SLPDV ;
SLPB1: LDS R16,$A4
SBRS R16,3
RJMP SLRET ;20点平均时间未到
SLPB2: SUBI R26,42 ;指针退回42字节,指向20点平均最旧数据
CPI R26,$50 ;不小于80,未超出采样数据存储区
BRCC SLPB20
SUBI R26,-80 ;否则加80调整回$150-$19F
SLPB20: LD R11,X+ ;
LD R10,X
SUB R4,R11
SBC R3,R10 ;找到20点平均最旧采样,并将其从累加和中减去!
BRCC SLPDV
DEC R1
SLPDV: LDI R16,20
MOV R11,R16
CLR R10
CLR R12
MOV R13,R1
MOV R14,R3
MOV R15,R4
RCALL DIV165 ;20点平均在r14r15中
SLRET: POP R27
POP R26
RET
;范例56 ;断电保护芯片MAX704,/RESET脚接8515同名脚
;/PFO接INT0,由VOUT脚给UT6264(或UT62256)/62x42x供电,
;本程序不涉及休眠!
.ORG $000 ;AT90S8515/时钟4MHZ
STRT60: RJMP RST60
RJMP EX_INT0 ;外部中断0
RJMP EX_INT1 :外部中断1
.ORG $009 ;uart_rxc interrupt
RJMP RCVSV
.ORG $010
RST60: LDI R16,2
OUT SPH,R16
LDI R16,$5f
OUT SPL,R16 ;堆栈指针初始化,指向$25f
CLR XH
LDI XL,$60
CLR R16
CLRX: ST X+,R16
CPI XL,$5E
BRNE CLRX
CPI XH,2
BRNE CLRX ;清除$60--$25d
LDI R16,$F0
OUT DDRB,R16 ;PB3-PB0输入 PB7-PB4输出
OUT PORTB,R16 ;上拉PB7-PB4
SBI PORTB,0
SBIS PINB,0
RJMP BG1A ;若将PB0接地,不做断电启动
LDI R16,70
CLR R12
CLR R11
DLOPX: DEC R11
BRNE DLOPX
DEC R12
BRNE DLOPX
DEC R16
BRNE DLOPX ;延时3.4秒(clk 4mhz)
CLR R27
LDI R26,$60
CLR R16
LOPX1: ST X+,R16
CPI R26,$5E
BRNE LOPX1
CPI R27,2 ;清除$60--$25d
BRNE LOPX1
CLI
LDI R16,$80
OUT GIMSK,R16 ;int1中断使能
LDI R16,$DA ;激活外部RAM,加1等待周期,不休眠,int0/int1下降沿有效
OUT MCUCR,R16
;.
;.
;. ;
LDS R16,$9FFE ;片外sram $8000-$9fff)
CPI R16,$55
BRNE BG1A ;查断电标志
LDS R16,$9FFF
CPI R16,$AA
BREQ BG2B ;查到
BG1A: LDI R27,$80
LDI R26,0
CLR R16
CLOPX: ST X+,R16
CPI R27,$A0
BRNE CLOPX ;清除$8000--$9FFF
LDI R16,$AA
ST -X,R16 ;$AA-->($9fff)
COM R16
ST -X,R16 ;$55-->($9ffe)
BG1A0: IN R16,GIMSK
SBR R16,$40
OUT GIMSK,R16 ;允许int0中断
RJMP NRMST
BG2B: LDS R16,$9FFD ;$9FFD:最高位为生产标志
SBRS R16,7
RJMP NRMST ;无生产标志转平常启动
BG5C: CBI PORTB,7 ;指示断电启动
LDI R27,$80
LDI R26,2 ;SRAM 8002-825F传回片内
LDI R29,0
LDI R28,2
CLR R0 ;检查和清除
APX0: LD R1,X+
ST Y+,R1 ;传送数据块
ADD R0,R1
CPI R28,26 ;指向r26?
BRNE APX0
LD R1,X+ ;取r26
ADD R0,R1
LD R1,X+ ;取r27
ADD R0,R1
LD R1,X+ ;取r28
ADD R0,R1
LD R1,X+ ;取r29/ r26--r29为数据指针,不能当作数据传送
ADD R0,R1
LDI R28,30
APX2: LD R1,X+
ADD R0,R1
ST Y+,R1
CPI R28,$5F
BRNE APX2
INC XL
INC YL ;SREG不断变化,不能加入累加和!
APX3: LD R1,X+
ADD R0,R1
ST Y+,R1
CPI R28,$60
BRNE APX3
CPI R29,2
BRNE APX3 ;到$25f?
LDS R1,$9FFC ;取检查和
ADD R0,R1 ;检查和(CHECKSUM)正确?
BREQ BG5D
RJMP BG1A ;错,转总清
BG5D: WDR
LDI R16,$0D ;看门狗初始化,溢出时间0.49"
OUT WDTCR,R16
CLR R2 ;调DSPA次数计数器清除
IN R16,GIMSK
SBR R16,$40
OUT GIMSK,R16 ;允许int0中断
LDS R26,$235
OUT SPH,R26
LDS R26,$234
OUT SPL,R26
POP R26
POP R27
POP R28
POP R29 ;数据指针出栈
POP R1
OUT SREG,R1 ;
POP R1
POP R0
RETI ;弹出断点,开放中断
NRMST: WDR
LDI R16,$0D ;看门狗初始化,溢出时间0.49"
OUT WDTCR,R16
CLR R2
;.......
SEI
;(略)
RCVSV: ;.
;.
;.
EX_INT0:PUSH R0 ;断电中断服务 I BE CLEARED!
PUSH R1
IN R1,SREG
PUSH R1
PUSH R29
PUSH R28
PUSH R27
PUSH R26 ;保护X,Y指针
LDI R26,$1D
OUT WDTCR,R26
LDI R26,$15
OUT WDTCR,R26 ;禁止看门狗
IN R26,SPL
STS $234,R26
IN R26,SPH
STS $235,R26 ;保护堆栈指针
LDI R27,0
LDI R26,2
LDI R29,$80
LDI R28,2 ;SRAM $002-25F 转片外$8002-$825f
CLR R0 ;检查和予清除
ALPX1: LD R1,X+
ST Y+,R1
ADD R0,R1 ;加入累加和
CPI R26,26 ;
BRNE ALPX1 ;
POP R1 ;R26~R29从堆栈中取!
ADD R0,R1
ST Y+,R1
POP R1 ;取R27
ADD R0,R1
ST Y+,R1
POP R1 ;取R28
ADD R0,R1
ST Y+,R1
POP R1 ;取R29
ADD R0,R1
ST Y+,R1
IN R26,SPL
SUBI R26,4 ;恢复堆栈指针,抵消4个POP
OUT SPL,R26
LDI R26,30 ;越过R26-R29,指向R30
APX10: LD R1,X+
ST Y+,R1
ADD R0,R1
CPI R26,$5F
BRNE APX10
INC XL ;SREG 越过!
INC YL
APX20: LD R1,X+
ST Y+,R1
ADD R0,R1
CPI R26,$60
BRNE APX20
CPI R27,2
BRNE APX20 ;完成到$8002-825F之转移
NEG R0
STS $9FFC,R0 ;SAVE THE CHECKSUM TO $9FFC
LDI R26,62
CLR R27
CLR R28
DLPX5: DEC R28
BRNE DLPX5
DEC R27
BRNE DLPX5
DEC R26
BRNE DLPX5 ;延时3秒(49.16ms*62=3")
LDI R27,$80
LDI R26,2 ;$8002-$825F
LDI R29,0
LDI R28,2 ;$002-25F
CLR R0
APX1A: LD R1,X+
ST Y+,R1 ;将片外SRAM数据传回片内
ADD R0,R1
CPI R28,26
BRNE APX1A
LD R1,X+ ;R26
ADD R0,R1
LD R1,X+ ;R27
ADD R0,R1
LD R1,X+ ;R28
ADD R0,R1
LD R1,X+ ;R29
ADD R0,R1
LDI R28,30
APX1B: LD R1,X+
ST Y+,R1
ADD R0,R1
CPI R26,$5F
BRNE APX1B
INC XL ;越过SREG!
INC YL
APX2A: LD R1,X+
ADD R0,R1
ST Y+,R1
CPI R28,$60
BRNE APX2A
CPI R29,2
BRNE APX2A ;到$25f?
LDS R1,$9FFC
ADD R0,R1
BRNE ERRDL
RJMP BG5D ;检查和正确
ERRDL: (略) ;错误处理
;范例57
;使用干电池便携系统断电保护程序 MAX704 RESET引脚接8535同名脚
;/PFO接8535INT0 断电时由电池给AT90LS8535供电
;晶振4MHZ
.ORG $000 ;AT90LS8535只使用片内sram;在片内RAM中保护数据
STRT61: RJMP RST61
RJMP EX_INT0
RJMP EX_INT1
.ORG $00B
RJMP RVCMPLT ;串行数据接收完成
.ORG $011
RST61: LDI R16,$00
OUT DDRA,R16 ;PA7-PA0为输入
LDI R16,21
CLR R12
CLR R13
DLPX: DEC R13
BRNE DLOPX
DEC R12
BRNE DLPX
DEC R16
BRNE DLOPX ;延时1秒(clk 4mhz)
LDI R16,2
OUT SPH,R16
LDI R16,$5f
OUT SPL,R16 ;堆栈指针$25f
CLR R2 ;调DSPB次数预清除
WDR
LDI R16,$0D ;设置看门狗溢出时间0.49"
OUT WDTCR,R16
LDI R16,$0F
OUT PORTA,R16
IN R16,PINA
CBR R16,$F0 ;清除无用的高4位
CPI R16,15
BRNE BG3A ;K0-K3有键按下,转
LDS R16,$23E ;
CPI R16,$55
BRNE BG3A
LDS R16,$23F
CPI R16,$AA
BRNE BG3A ;无断电标志,转
CLR R16
STS $23E,R16 ;清除断电标志
CLI
LDI R16,$C0
OUT GIMSK,R16 ;
LDI R16,$60 ;掉电休眠,
OUT MCUCR,R16 ;int0 INT0 电平中断
;.
;. ;其他初始化程序略
;. ;
RJMP REST2 ;转断电启动!
RVCMPLT:;(MISSING)
BG3A: CLR R27
LDI R26,$60
CLR R16
LOPX1: ST X+,R16
CPI R26,$60
BRNE LOPX1
CPI R27,2
BRNE LOPX1 ;清除$60--$25f
LDI R16,2
OUT SPH,R16
LDI R16,$5FH
OUT SPL,R16
CLI
LDI R16,$C0
OUT GIMSK,R16 ;允许int0/int1中断
LDI R16,$60 ;掉电休眠
OUT MCUCR,R16 ;INT0/INT1 电平中断
;.
;. ;其他初始化程序略
;.
SEI
HH61: RCALL DSPB ;液晶显示子程序略
LDI R16,$0F ;激活上拉电阻
OUT PORTA,R16 ;
IN R16,PINA ;读入键状态
CBR R16,$F0
CPI R16,$0F ;有键按下?
BRNE HH61 ;等待释放
IN R16,TIMSK
SBR R16,$C0
OUT TIMSK,R16 ;重新允许INT1中断
SLEEP ;进入掉电休眠
RJMP HH61 ;唤醒后显示新采集的数据
EX_INT1:SEI ;允许INT0中断
PUSH R16 ;K0/K1/K2/K3有按下者,产生电平中断唤醒MCU,采集数据
IN R16,SREG
PUSH R16
SBI PORTA,3
SBIS PINA,3
RJMP DLK63 ;K3按下采集数据
SBI PORTA,2
SBIS PINA,2
RJMP DLK62 ;K2按下采集数据
SBI PORTA,1
SBIS PINA,1
RJMP DLK61 ;K1按下采集数据
RJMP DLK60 ;K0按下采集数据
DLKRT: IN R16,TIMSK
CBR R16,$80
OUT TIMSK,R16 ;禁止INT1中断(键未释放或抖动时不引起中断)
POP R16
OUT SREG,R16
POP R16
RETI
DLK60: ;. ;采集、处理数据,数据处理后送入显示缓存区
;.
;.
RJMP DLKRT
DLK61: ;. ;采集、处理数据,数据处理后送入显示缓存区,
;.
;.
RJMP DLKRT
DLK62: ;. ;采集、处理数据,数据处理后送入显示缓存区
;.
;.
RJMP DLKRT
DLK63: ;. ;采集、处理数据,数据处理后送入显示缓存区
;.
;.
RJMP DLKRT
EX_INT0:PUSH R0 ;掉电中断服务子程序
PUSH R2
PUSH R12
PUSH R13 ;CLI ALREADY!
PUSH R14
PUSH R15
PUSH R16
PUSH R17
PUSH R26
PUSH R27
PUSH R30
PUSH R31
IN R16,SREG
PUSH R16 ;保护状态寄存器
LDI R16,$1D
OUT WDTCR,R16
LDI R16,$15
OUT WDTCR,R16 ;停止看门狗
IN R16,SPL
STS $23C,R16
IN R16,SPH
STS $23D,R16 ;保护堆栈指针
LDI R16,$55
STS $23E,R16
COM R16
STS $23F,R16 ;写断电标志
SER R16
OUT PORTC,R16 ;关显示
CLR R16
OUT GIMSK,R16 ;禁止外部中断(INT0&INT1)
SLEEP ;进入掉电休眠
REST2: LDS R16,$23D
OUT SPH,R16
LDS R16,$23C
OUT SPL,R16 ;取出堆栈指针
POP R16
OUT SREG,R16 ;恢复状态寄存器
POP R31
POP R30
POP R27
POP R26
POP R17
POP R16
POP R15
POP R14
POP R13
POP R12
POP R2
POP R0 ;恢复工作寄存器,主程序初始化时只能使用这些寄存器!
RETI ;弹出断点,开放中断
;范例58
.EQU DPOINT=$100 ;DATA BLOCK from $100 to $22b
CRCST: LDI R16,2 ;最末2字节在发送方已清为零(或仍为$0D$0A)
MOV R11,R16 ;在接收方则为对方计算出的CRC校验码(余式)
LDI R16,$2C
MOV R12,R16 ;(r11r12)内装入$22C,块长为$12C
CRCST1: LDI R26,HIGH(DPOINT)
LDI R27,LOW(DPOINT);数据指针
CRC0: CLR R14
CLR R15
LDI R17,$80 ; 16 15 2
LDI R18,$05 ;P(X)=X +X +X +1=$18005
CRC1: LDI R16,8
MOV R13,R16 ;8位/字节
LD R16,X+
CRC2: LSL R16
ROL R15
ROL R14
BRCC CRC3
EOR R14,R17
EOR R15,R18 ;移出位为1时,将寄存器r14r15内容异或立即数$8005
CRC3: DEC R13 ;位数减1
BRNE CRC2
DEC R12 ;字节数减1
BRNE CRC1
DEC R11
BRNE CRC1
ST -X,R15
ST -X,R14 ;除得余数放在数据块尾部(或将原始数据恢复)!
RET
;范例58A ;DS18B20读出温度数据CRC检测子程序,生成多项式为P(X)=X8+X4+X3+1
CRCSTA:LDI XL,$70 ;温度数据指针
CLR XH
LDI R16,9 ;温度数据,上、下限......CRC校验码等共9字节
CLR R15 ;异或除法工作单元
LDI R18,$8C
CRC1A: LD R14,X+
LDI R17,8
CRC2A: LSR R14
ROR R15 ;位序列右移
BRCC CRC3A
EOR R15,R18 ;移出位为1时,位序列异或立即数$8C
CRC3A: DEC R17
BRNE CRC2A ;右移次数减1
DEC R16
BRNE CRC1A ;块长减1
RET ;(R15)=0 接收正确!
;范例59
DEMCRC: LDI R27,1 ;CRC演示程序(校验码16位)
CLR R26 ;数据块首地址为$100
DEMLP: ST X+,R26 ;
CPI R26,$2A ;在$100-$229中充入数据
BRNE DEMLP
CPI R27,2
BRNE DEMLP ;$100--$229中充入$00--$FF和$00-$29
CLR R16
ST X+,R16
ST X,R16 ;$22A,$22B两单元请除,将计算出余式(即CRC校验码)放在其中
RCALL CRCST ;在发送方计算出CRC校验码
RETEST: RCALL CRCST ;在接收方做CRC检测(余式在r14r15)
OR R15,R14 ;r14r15恢复为$0000(或恢复出原数据为正确接收)
BRNE ERCRC
HCRC: RJMP HCRC
ERCRC: ;. ;出错处理,要求对方重发
;.
RJMP RETEST ;重新CRC检测
.DSEG
.ORG $100
DPOINT: .BYTE $12C
; $00 $01 $02 $03 $04 $05 $06 $07 $08 $09 $0a $0b $0c $0d $0e $0f
; $10 $11 $12 $13 $14 $15 $16 $17 $18 $19 $1a $1b $1c $1d $1e $1f
; $20 $21 $22 $23 $24 $25 $26 $27 $28 $29 $2a $2b $2c $2d $2e $2f
; $30 $31 $32 $33 $34 $35 $36 $37 $38 $39 $3a $3b $3c $3d $3e $3f
; $40 $41 $42 $43 $44 $45 $46 $47 $48 $49 $4a $4b $4c $4d $4e $4f
; $50 $51 $52 $53 $54 $55 $56 $57 $58 $59 $5a $5b $5c $5d $5e $5f
; $60 $61 $62 $63 $64 $65 $66 $67 $68 $69 $6a $6b $6c $6d $6e $6f
; $70 $71 $72 $73 $74 $75 $76 $77 $78 $79 $7a $7b $7c $7d $7e $7f
; $80 $81 $82 $83 $84 $85 $86 $87 $88 $89 $8a $8b $8c $8d $8e $8f
; $90 $91 $92 $93 $94 $95 $96 $97 $98 $99 $9a $9b $9c $9d $9e $9f
; $a0 $a1 $a2 $a3 $a4 $a5 $a6 $a7 $a8 $a9 $aa $ab $ac $ad $ae $af
; $b0 $b1 $b2 $b3 $b4 $b5 $b6 $b7 $b8 $b9 $ba $bb $bc $bd $be $bf
; $c0 $c1 $c2 $c3 $c4 $c5 $c6 $c7 $c8 $c9 $ca $cb $cc $cd $ce $cf
; $d0 $d1 $d2 $d3 $d4 $d5 $d6 $d7 $d8 $d9 $da $db $dc $dd $de $df
; $e0 $e1 $e2 $e3 $e4 $e5 $e6 $e7 $e8 $e9 $ea $eb $ec $ed $ee $ef
; $f0 $f1 $f2 $f3 $f4 $f5 $f6 $f7 $f8 $f9 $fa $fb $fc $fd $fe $ff
; $00 $01 $02 $03 $04 $05 $06 $07 $08 $09 $0a $0b $0c $0d $0e $0f
; $10 $11 $12 $13 $14 $15 $16 $17 $18 $19 $1a $1b $1c $1d $1e $1f
; $20 $21 $22 $23 $24 $25 $26 $27 $28 $29 $00 $00
;码制转换
;范例60
;地址 $90 1 2 3 4 5 6 7 8 9 a b c d e f $a0
;ascii码数据 P i , A S , +/- x x x x . x x k g
.EQU DPNT=$90 ;the first ascii character addr.
;DataPoint(.'sit):$a0/weighing unit:$a1,$a2
;P1-P4'no.:$a6$a7--$ac$ad/max.no.:$ae$af
.EQU TPTR=$b0 ;the first total(total1($b0-$b3)) addr.
.EQU CPTR=$C0 ;print char. buffer addr.
.EQU SREG=$3F
.EQU SPH=$3E
.EQU SPL=$3D
ACUM: CLR R29
CLR R27 ;ASCII码存放区为$90-9F
LDI R26,$90
LD R16,X
CPI R16,$50 ;P打头方为有效
BRNE ACRT
ADIW R26,2 ;指向$92
LD R16,X
CPI R16,$2C ;是','?
BREQ DOP0
ACRT: SET ;非法数据!
RET
DOP0: LDI R17,4
LDI R26,$99 ;设指针,寻找小数点
DOP1: LD R16,X+
CPI R16,$2E
BREQ DOP3 ;找到'.'
DOP2: DEC R17
BRNE DOP1
DOP3: LDI R28,$A0 ;小数点放入$A0,1,2,3,4表示小数点后有1,2,3,4位数据
ST Y+,r17 ;0表示无小数点
LDI R26,$9E ;指向质量单位
LD R17,X+
ST Y+,R17
LD R17,X+
ST Y,R17 ;质量单位(kg/ t)-->$a1,$a2
LDI R28,11
CLR R9
CLR R10
CLR R11 ;予请除,存放BCD码
LDI R26,$9E
F1: LD R16,-X ;减1后指向$9d!
CPI R16,$2E ;从低位到高位顺序将ASCII转为BCD,两两合成1字节压缩BCD码
BREQ F1 ;遇到小数点跳过
F2: BRCS FEND ;遇空格/+/-等结束
SUBI R16,$30 ;十进制数ascii变bcd
MOV R12,R16
ST Y,R16
F3: LD R16,-X
CPI R16,$2E
BREQ F3
F4: BRCS FEND ;小于$2E转换结束
SUBI R16,$30
SWAP R16
ADD R16,R12
ST Y,R16
DEC R28
CPI R28,8
BRNE F1
FEND: MOV R17,R9
OR R17,R10
OR R17,R11
BREQ ACRT ;0数据转出
RCALL CONV2 ;整数二翻十(r9r10r11-->r13r14r15)
MOV R5,R13
MOV R6,R14
MOV R7,R15
CLR R12
LDS R16,$96 ;取数据符号
CPI R16,$2d ;'-'ASCII码
BRNE F09
LDI R26,16
RCALL NEG4 ;负数取补
F09: LDI R26,$91 ;指向数据序号ASCII码
LD R16,X+
SUBI R16,$31 ;将ASCII码序号$31--$34变为0--3,
CPI R16,4
BRCC FRET ;大于3为无效
MOV R9,R16 ;暂存
LSL R16
LSL R16 ;乘4
LDI R26,$B0 ;$B0为第一个累加和首地址(TPTR)
ADD R26,R16 ;得到实际首地址
LDI R16,4
LDI R28,16 ;数据指针
CLC
LACM: LD R17,X ;取累加和一字节数据
LD R10,-Y
ADC R17,R10
ST X+,R17
DEC R16
BRNE LACM ;r12,13,14,15 加入累加和
LSL R9 ;序号乘2
LDI R26,$AE ;指向最大累加次数
LDI R28,$A6 ;指向第一个累加次数
ADD R28,R9 ;指向实际累加次数
LD R11,X+
LD R10,X ;取最大累加次数(2字节)
LD R13,Y
INC R13 ;实际累加次数增1
ST Y+,R13 ;低位字节送回
LD R12,Y
TST R13
BRNE F10
INC R12 ;低位字节增1后为0,高位字节增1
ST Y,R12
F10: SUB R11,R13
SBC R10,R12 ;与最大累加次数相比较
BRCC F12
ST X,R12
ST -X,R13 ;存最大累加次数
F12: MOV r15,r7
MOV r14,r6
MOV r13,r5
LDI R17,$98 ;予设阶码(假定为24位整数)
MOV R12,R17
F120: SBRC R13,7
RJMP F13
LSL R15
ROL R14
ROL R13
DEC R12
RJMP F120
F13: LDS R0,$A0 ;取小数点位数(0,1,2,3,4)
TST R0
BREQ F14 ;整数转
F130: RCALL G01 ;取浮点数0.1(范例70)
RCALL FPMU ;(范例65)
DEC R0
BRNE F130 ;小数点位置决定乘几个0.1
F14: LDS R16,$96
CPI R16,$2B ;负数?
BRNE F9
LDI R16,$7F
AND R13,R16 ;正数清除数符位
F9: CLT ;合法数据出口(T=0)
RET
FRET: SET ;非法数据出口(T=1)
RET
NEG4: LDI R16,4 ;4字节二进制数据求补
CLC
NG4L: CLR R17
LD R11,-X ;X-1指向最低位字节
SBC R17,R11
ST X,R11
DEC R16
BRNE NG4L
RET
FLSPC: LDI R26,$C0 ;准备一行空格字符,为打印一行空格做准备
CLR R31
LDI R16,$20 ;SPC
FSLOP: ST X+,R16
CPI r30,$d0
BRNE FSLOP
LDI R16,$0D ;$0D-->($D0)
ST X+,R16
LDI R16,$0A ;$0A-->($D1)
ST X+,R16
RET
BRDT: RCALL CONV1A ;二翻十并将压缩BCD码转换为ASCII码
RCALL FLSPC
LDI R28,$A3 ;$A1,$A2 is the weighing unit
CLR R29 ;取质量单位到打印数据存储区
LDI R26,$D0
LD R16,-Y
ST -X,R16 ;'g'-->($D0-1)
LD R16,-Y
ST -X,R16 ;取质量单位:($A1)-->$ce &($A2)-->$CF
LD R10,-Y ;取小数点位置:($A0)->R10
LP59: LDI R28,15
LP60: LD R16,Y
RCALL BTOA ;低位BCD变为ASCII码
LD R16,Y
RCALL BTOA0 ;高位BCD变为ASCII码
DEC R28
CPI R28,10 ;r11,12,13,14,15 都分解完毕?
BRNE LP60
DL30H: LDI R26,$C5
BRTC DL300 ;数据为负?
LDI R16,$2D
ST X,R16 ;负数加'-',送入$C5
CLT ;并清除负数标志
DL300: INC R26
LD R16,X
CPI R16,$30
BRCS DL300 ;去掉数据头无效的零ASCII码$30
DL301: CPI R16,$30
BRNE DLRT ;非零结束
INC R26
LD R16,X
CPI R16,$30
BRCS DLRT ;小于$30结束(质量单位 t)
CPI R16,$3A
BRCS DL302 ;大于$3A结束(质量单位kg)
DLRT: RET
DL302: DEC R26
LDI R16,$20 ;无效零充以空格
ST X+,R16
LD R16,X
RJMP DL301
BTOA0: SWAP R16
BTOA: ANDI R16,15
SUBI R16,$D0 ;加$30变为ASCII码
ST X,R16
DEC R26
DEC R10
BRNE BART
LDI R16,$2E ;加入小数点ASCII码
ST X,R16
DEC R26
BART: RET
PRAV: LDI R17,4 ;打印4组平均数据
MOV R0,R17
DEC R0
PRV: MOV R17,R0
LSL R17
LSL R17 ;组别序号之偏移量
LDI R26,$B0 ;TOTAL1首趾
ADD R26,R17 ;得到实际组别之首地址
LDI R28,16 ;将TOTAL取入 R12R13R14&R15
CLR R10
BRV: LD R16,X+
ST -Y,R16
OR R10,R16
NOC: CPI R28,12
BRNE BRV
TST R10
BREQ NBR ;TOTAL为零不打印!
BRV1: BST R12,7 ;数符位送入T
BRTC BRV2
LDI R26,16
RCALL NEG4 ;负数求补
BRV2: MOV R26,R0
LSL R26
SUBI R26,-$A6 ;取本组累加和之累加次数(第一组从$A6开始)
LD R11,X+
LD R10,X+
RCALL DIV24 ;计算平均值(在r13r14r15中)
CLR R7
MOV R8,R13
MOV R9,R14
MOV R10,R15 ;r11r12r13r14r15<--r7r8r9r10
RCALL BRDT ;二翻十,再将BCD码转为ASCII码,如为负数将'-'装入$c5!
LDI R26,$C5
LDI R16,$56 ;'V'
ST -X,R16 ;start from $c4!
LDI R16,$41 ;'A'
ST -X,R16
LDI R16,$2C ;','
ST -X,R16
MOV R16,R0 ;i(=1/2/3/4)
SUBI R16,$CF ;加上$31
ST -X,R16
LDI R16,$50
ST -X,R16 ;'P'
RCALL PR1 ;打印一行
NBR: DEC R0
BRNE PRV
RET
PRNO: RCALL FLSPC ;打印最大累加次数(整数),不加小数点
LDI R26,$CD ;存放ASCII码指针
LDI R28,$AE ;指向最大累加次数
LD R10,Y+
LD R9,Y ;最大累加次数取到R9R10
CLR R8
CLR R7
RCALL CONV1A ;二进制数变为BCD码
BRN: CLR R10 ;不加 '.'
RCALL LP59 ;转为ASCII码
LDI R26,$C4
LDI R16,$2E ;'.'
ST -X,R16
LDI R16,$4F ;'O'
ST -X,R16
LDI R16,$4E
ST X,R16 ;'N',加上‘NO.’后
RJMP PR1 ;打印
PRTL: LDI R17,4
MOV R0,r17 ;取序号之偏移量
DEC R0
PRL: MOV R17,R0
LSL R17
LSL R17 ;乘4:累加和为4字节
CLR R27
LDI R26,$B0 ;第一个累加和TOTAL1之首地址
ADD R26,R17 ;累加和之实际地址
CLR R15
LDI R28,11
BRL: LD R16,X+
ST -Y,R16
OR R15,R16
CPI R28,7
BRNE BRL ;累加和取到r7r8r9r10
NINC: TST R15
BREQ NBL ;累加和为零,不打印
BST R7,7
BRTC BRTL1
LDI R26,11
RCALL NEG4 ;累加和为负数,取补
BRTL1: RCALL BRDT ;BCD转为ASCII
LDI R26,$C5
LDI R16,$4C ;加'L'
ST -X,R16
LDI R16,$54 ;加'T'
ST -X,R16
LDI R16,$2C
ST -X,R16 ;加','
MOV R16,R0
SUBI R16,$CF ;i(=1/2/3/4)/加上$30变为ASCII码
ST -X,R16
LDI R16,$50
ST -X,R16 ;加'P'
RCALL PR1 ;打印一行累加和数据(一个TOTAL)
NBL: DEC R0
BRNE PRL ;共打印4行
RET
PR1: LDI R26,$C0 ;打印区首地址
CLR R27
LDI R16,$28 ;允许UART发送,允许发送寄存器空中断,8位数据
OUT UCR,R16
SEI ;使能总中断
RET
CONV1A: LDI R17,32 ;整数二翻十(最大$FFFFFFFF=4294967295)
MOV R0,R17
CLR R11
CLR R12 ;r11r12r13r14r15(BCD)<--(r7r8r9r10二进制)
CLR R13
CLR R14
CLR R15 ;十进制数存储区清除
CV1A: LSL R10
ROL R9
ROL R8
ROL R7 ;二进制数左移一位
MOV R16,R15
RCALL LSDAA
MOV R15,R16
MOV R16,R14
RCALL LSDAA
MOV R14,R16
MOV R16,R13
RCALL LSDAA
MOV R13,R16 ;十进制数带进位左移并调整
MOV R16,R12
RCALL LSDAA
MOV R12,R16
MOV R16,R11
RCALL LSDAA
MOV R11,R16
DEC R0
BRNE CV1A
RET
;范例61 ;格雷码与二进制数相互转换
GTB8: LDI R16,7 ;8格雷码(在R17中)翻为二进制数
CLR R15
LSL R17
ADC R17,R15 ;将左移移出位加到末位上
GB1: SBRC R17,0
SUBI R17,$80 ;Bi⊕G(i+1)-->B(i+1)
GB2: LSL R17
ADC R17,R15 ;将左移移出位加到末位上
DEC R16
BRNE BG1 ;循环7次,结束
RET ;二进制数在r17中
GTB9: LDI R16,8 ;9位格雷码(最高位在进位C,低8位在R17中)翻为二进制数
CLR R15
INC R15 ;1-->r15
ROL R17 ;9位格雷码带进位循环左移一位
GB90: ROL R17 ;the ORIGINAL highest bit->r17,1 AT THE FIRST!
SBRC R17,1
EOR R17,R15 ;Bi⊕G(i+1)-->B(i+1)
DEC R16
BRNE GB90
RET ;结果仍在进位C和r17中
BTG8: LDI R16,7 ;8位二进制数翻为格雷码
CLR R15
INC R15 ;1-->r15
CLC
ROL R17 ;0-->r17,0&B1-->C
BRCC BGLOP
INC R17 ;B1-->r17.0
BGLOP: ROL R17
BRCC BG4
EOR R17,R15 ;Bi⊕B(i+1)-->G(i+1)/FOR EXAMPLE:(1110-->1001)
BG4: DEC R16
BRNE BGLOP
RET ;结果在R17
AVR浮点程序库
;范例62
.ORG $A00
EXCH: MOV R5,R8 ;两浮点数交换子程序
MOV R8,R12
MOV R12,R5
EXCH1: MOV R5,R9 ;尾数交换
MOV R9,R13
MOV R13,R5
MOV R5,R10 ;双字节交换
MOV R10,R14
MOV R14,R5
MOV R5,R11
MOV R11,R15
MOV R15,R5
RET
DP: ANDI R16,$7F ;处理积/商数符,计算积/商阶码子程序
SBRC R9,7
SUBI R16,$80
SBRC R13,7
SUBI R16,$80 ;积/商符号放在r16,7
ADD R12,R8 ;移码相加(除数阶码已求补)
LDI R17,$80
BRCC DP1
ADD R12,R17 ;移码求和有进位,将和再加上$80,再有进位为溢出
RET
DP1: SUB R12,R17 ;移码求和无进位,将和减去$80,有借位
RET ;或差为0也为溢出
NEG3: COM R15 ;3字节数据求补
COM R14 ;先求反后加1
COM R13
INC3: LDI R17,255
SUB R15,R17 ;以减去-1代替加1
SBC R14,R17
SBC R13,R17
RET
;范例63 ;浮点数比较大小子程序 X1为被减数 X2为减数
FPCP: SBRC R9,7 ;X1为正,跳行
RJMP CP1
SBRC R13,7 ;X2为正,跳行
RJMP CP2 ;X1,X2异号
FPCP1: CP R11,R15 ;X1,X2皆为正,以尾数低位字节,中位字节,高位字节和
CPC R10,R14 ;阶码的顺序(按无符号数)进行比较
CPC R9,R13 ;不等,阶码大者浮点数值也大;只有阶码和尾数对应相等,
CPC R8,R12 ;两浮点数才相等
RET ;比较结果:Z=1时X1=X2,否则C=0时X1>X2,C=1时X1<X2
CP1: SBRC R13,7
RJMP CP3 ;两负数比较,转
CP2: CP R13,R9 ;正数与负数比较,只比较尾数高位字节即可
RET
CP3: CP R15,R11 ;X1,X2皆为负,以尾数低位字节,中位字节,高位字节和
CPC R14,R10 ;阶码的顺序(按无符号数)进行比较
CPC R13,R9 ;但要将X1、X2交换位置后按正数比较过程进行
CPC R12,R8
CP4: RET ;比较结果:Z=1时X1=X2,否则C=0时X1>X2,C=1时X1<X2
;范例64
FPSU: LDI R17,$80 ;浮点减法子程序
SUB R13,R17 ;减数数符求反后作为加数
FPAD: TST R8 ;浮点加法子程序
BREQ DON1 ;被加数为0,加数为和
TST R12
BRNE FPLAD ;加数为0,取被加数为和
SAV0: MOV R12,R8 ;传送被加数取代加数
MOV R13,R9
MOV R14,R10
MOV R15,R11
DON1: RET
FPLAD: ANDI R16,$3f ;清除被加数,加数数符
SBRC R9,7
ORI R16,$80 ;被加数数符取到(R16,7)
SBRC R13,7
ORI R16,$40 ;加数数符取到(R16,6)
LDI R17,$80
OR R9,R17
OR R13,R17 ;恢复尾数最高位
MOV R17,R12
SUB R17,R8 ;计算阶差
BREQ GOON ;两阶相等,转
BRCC NX3
NEG R17 ;不够减求补
CPI R17,24
BRCC EXAD ;|阶差|>24,取被加数为和
NX2A: LSR R13
ROR R14
ROR R15
DEC R17
BRNE NX2A ;加数阶小,右移加数对阶
MOV R12,R8 ;取被加数阶为和之阶
BRCC GOON
RCALL INC3 ;舍入移出位
RJMP GOON
NX3: CPI R17,24
BRCC COM1 ;阶差>24,取加数为和
LOOP: LSR R9
ROR R10
ROR R11
DEC R17
BRNE LOOP ;加数阶大,右移被加数对阶
BRCC GOON
RCALL INC3A ;舍入移出位
GOON: SBRC R16,6
SUBI R16,$80
SBRS R16,7 ;判别两数是否同号
RJMP SAMS ;同号转
SUB R15,R11 ;异号,执行减法,加数为被减数
SBC R14,R10
SBC R13,R9
BRCC NOM ;够减转
SUBI R16,$40 ;否则被减数数符求反为和之数符
RCALL NEG3 ;并将差求补
NOM: MOV R17,R13
OR R17,R14
OR R17,R15
BREQ DON0 ;差为0转
NMLOP: SBRC R13,7
RJMP COM1
LSL R15
ROL R14
ROL R13
DEC R12
BRNE NMLOP ;规格化
OV1: SEV ;阶码变为0,下溢(可取为0,不算溢出)
RET
SAMS: ADD R15,R11
ADC R14,R10
ADC R13,R9 ;两数同号,执行加法
BRCC COM1
ROR R13
ROR R14
ROR R15
INC R12 ;有进位时右规1次($7F+1=$80溢出)
BREQ OV1 ;阶码增1后变为0为上溢
BRNC COM1
RCALL INC3
COM1: CLV
SBRC R16,6
RET
COMA: LDI R17,$7F
AND R13,R17 ;正数数符为0
DON: RET
EXAD: RCALL SAV0 ;取被加数为和
SBRS R16,7
RJMP COMA ;配置数符
RET
DON0: CLR R12 ;浮点数为0
RET
;范例65 ;浮点乘法子程序
FPMU: TST R8
BREQ M0 ;被乘数为0,积为0
TST R12
BRNE M1 ;乘数为0,积也为0
M0: RJMP G0
M1: RCALL DP ;处理积符号,计算积之阶码
BRCS OV2
BREQ OV2 ;判断溢出
LDI R17,$80
OR R9,R17
OR R13,R17 ;恢复尾数最高位
MOV R5,R13
MOV R6,R14
MOV R7,R15 ;乘数转入R5,R6,R7
LDI R17,25 ;设右移部分积次数
CLR R13
CLR R14
CLR R15 ;r13r14r15清除,存放积
CLC
LOOP1: BRCC M2
ADD R15,R11
ADC R14,R10
ADC R13,R9 ;乘数右移移出位为1,被乘数加入部分积1次
M2: ROR R13
ROR R14
ROR R15
ROR R5
ROR R6
ROR R7 ;部分积连同乘数右移1位
DEC R17
BRNE LOOP1 ;尾数相乘计算完成?
SBRC R13,7
RJMP M3 ;乘积最高位为1 转
ROL R5
ROL R15
ROL R14
ROL R13 ;乘积最高位为0,高4位字节左移1位
SBRS R5,7
RJMP M5
RCALL INC3 ;末位字节舍入
BRNE M5
SEC ;舍入后R13变为0
ROR R13 ;将其改为$80(即0.5)
RJMP COM2
M5: DEC R12 ;舍入后R13不为0
BRNE COM2 ;阶码减1
OV2: SEV ;变为0为溢出
RET
M3: SBRC R5,7
RCALL INC3 ;乘积低3位字节舍入
COM2: LDI R17,$7F
SBRS R16,7
AND R13,R17 ;正数将符号位请除
DON2: CLV
RET
;范例66
FPDI: TST R12 ;浮点除法子程序
BREQ OV3 ;除数为0,溢出
TST R8
BRNE D1
RJMP G0 ;被除数为0,商为0
D1: NEG R12 ;除数阶码求补,以加补码代替减原码
RCALL DP ;处理商符号,计算商之阶码
BRCS OV3
BREQ OV3 ;判断溢出
LDI R17,$80
OR R9,R17
OR R13,R17 ;恢复尾数最高位
FPD3: LDI R17,25 ;左移相减试商25次,最后1次舍入
SUB R11,R15
SBC R10,R14
SBC R9,R13
BRCS D2 ;第一次尾数相减试商
INC R12 ;够减,商阶增1
SEC
BRNE D3 ;商阶增1后不为0,转计商;否则为溢出
OV3: SEV
RET
D2: ADD R11,R15
ADC R10,R14
ADC R9,R13 ;不够减则恢复被除数
LOOP2: LSL R11
ROL R10
ROL R9 ;被除数算术左移
BRCS D4 ;进位位为1,够减,本位商1
SUB R11,R15
SBC R10,R14
SBC R9,R13 ;否则相减试商
BRCS D2A
SEC
RJMP D3 ;够减,本位商1
D2A: ADD R11,R15 ;不够减,恢复被除数
ADC R10,R14
ADC R9,R13
CLC ;本位商0
RJMP D3
D4: SUB R11,R15
SBC R10,R14
SBC R9,R13 ;被除数减去除数
D3: DEC R17
BRNE D5 ;除法未完成,循环(1-1=0,不溢出)
MOV R13,R5
MOV R14,R6
MOV R15,R7 ;取回商
BRCC COM3
RCALL INC3 ;第25位商舍入($800000-$FFFFFF不溢出,故INC3不会溢出!)
COM3: LDI R17,$7F
SBRS R16,7
AND R13,R17 ;配置商数符
DON3: RET
D5: ROL R7 ;在R5,R6,R7中记商(不必预先清除)
ROL R6
ROL R5 ;商数左移1位并记商
RJMP LOOP2
;范例67
FPSQ: ANDI R16,$7F ;模拟手算开平方子程序
SBRC R13,7
ORI R16,$80 ;负数 建虚根标志
FPS0: TST R12
BREQ DON4 ;0的平方根为0
LDI R17,$80
OR R13,R17 ;恢复尾数最高位
LSR R12 ;阶码算术右移1位
BRCC FSQ2
INC R12 ;移出位舍入
RCALL INC3 ;先将位数增1(提前舍入)
BRCS FSQ1 ;C=1,不够减
SEC
ROR R13 ;若尾数变为0将其改为0.5($80-->r13)
RJMP FSQ2
FSQ1: LSR R13
ROR R14
ROR R15 ;否则将为数算术右移
FSQ2: LDI R17,25 ;开出25位根,末位舍入
MOV R8,R17
LDI R17,$40
ADD R12,R17 ;根恢复为移码
CLR R5
CLR R6
CLR R7 ;根扩展区清除
CLR R9
CLR R10
CLR R11 ;根存储区清除
FSQ3: SUB R13,R17
SBC R7,R11
SBC R6,R10
SBC R5,R9 ;试根
BRCS FSQ3A
SEC
RJMP FSQ4 ;够减,本位根1
FSQ3A: ADD R13,R17
ADC R7,R11
ADC R6,R10
ADC R5,R9 ;否则恢复开平方数之尾数
CLC ;本位商0
FSQ4: DEC R8
BRNE FSQ5 ;开出第25位根?
FQDON: MOV R13,R9
MOV R14,R10
MOV R15,R11 ;回送根尾数
BRCC COM4
RCALL INC3 ;第25位根舍入
COM4: LDI R17,$7F
AND R13,R17 ;根尾数为正数
DON4: RET
FSQ5: ROL R11
ROL R10
ROL R9 ;根尾数带进位左移,记根
LSL R15
ROL R14
ROL R13
ROL R7
ROL R6
ROL R5
LSL R15
ROL R14
ROL R13
ROL R7
ROL R6
ROL R5 ;开平方数之尾数连同扩展区左移2位
BRCC FSQ3 ;未产生进位,循环
RJMP FQDON ;否则进位为第25位根(不须试,并结束子程序)!
;范例68 ;牛顿迭代开平方子程序
FSQR: TST R12
BREQ SQRT ;0的平方根为0
ANDI R16,$7E
SBRC R13,7
ORI R16,$80 ;虚根标志
SBRC R12,0
INC R16 ;阶码为奇数
LDI R17,$7F
AND R13,R17 ;尾数变为正数
LSR R12
LDI R17,$40
ADC R12,R17 ;得到根之移码
PUSH R12 ;暂存
LDI R17,$80
MOV R12,R17
SBRC R16,0
INC R12 ;得到X1的阶码(0.5≤X1<2)
RCALL LD1 ;存 X1
LSR R13
ROR R14
ROR R15
LDI R17,$40
SBRS R16,0 ;阶码为奇数时算术右移尾数即得到X1之尾数;否则将其最
;高位字节加上$40
OR R13,R17 ;得到首次根r0=(1+x1)/2
LDI R17,3
MOV R0,R17 ;迭代3次
FSQLP: RCALL LD2
RCALL GET1
RCALL FPDI
RCALL GET2
RCALL FPAD
DEC R12 ;计算r(i+1)=(x1/ri+ri)/2
DEC R0
BRNE FSQLP ;r3的尾数为根之尾数
POP R12 ;取回根之阶码
SQRT: RET ;r16,7=1 为虚数根
;范例69 ;基本运算程序的演示程序
DMST1: .EQU SPL=$3D
.EQU SPH=$3E
LDI R16,2 ;high(ramend)
OUT SPH,R16
LDI R16,$5F ;low(ramend)
OUT SPL,R16
LDS R11,$60 ;r11,7:数符 r11,6 :阶符 r11,5--0:阶(最大为38)
LDS R12,$61 ;r12-r15:尾数
LDS R13,$62
LDS R14,$63
LDS R15,$64 ;尾数共8位BCD码
RCALL DTOB ;转为二进制浮点数
RCALL LD2 ;暂存
LDS R11,$65 ;r11,7:数符 r11,阶符 r11,5--0:阶(最大为38)
LDS R12,$66 ;r12-r15:尾数
LDS R13,$67
LDS R14,$68
LDS R15,$69
RCALL DTOB ;转为二进制浮点数
RCALL GET2 ;取第一操作数
RCALL FPAD ;调基本运算子程序之一(FPSU/FPMU/FPDI)
RCALL BTOD ;转回十进制浮点数
DMRET: RJMP DMRET
;范例70 ;辅助子程序
KP2: MOV R8,R12 ;复制第二操作数
MOV R9,R13
MOV R10,R14
MOV R11,R15
RET
LD1: STS $70,R12 ;存浮点数
STS $71,R13
STS $72,R14
SYS $73,R15
RET
LD2: STS $74,R12 ;存浮点数
STS $75,R13
STS $76,R14
STS $77,R15
RET
LD3: STS $78,R12 ;存浮点数
STS $79,R13
STS $7A,R14
STS $7B,R15
RET
GET1: LDS R8,$70 ;取浮点数
LDS R9,$71
LDS R10,$72
LDS R11,$73
RET
GET2: LDS R8,$74 ;取浮点数
LDS R9,$75
LDS R10,$76
LDS R11,$77
RET
GET3: LDS R8,$78 ;取浮点数
LDS R9,$79
LDS R10,$7A
LDS R11,$7B
RET
INVPI: LDI R17,$86 ;取浮点数180/л
MOV R8,R17
LDI R17,$65
MOV R9,R17
LDI R17,$2E
MOV R10,R17
LDI R17,$E1
MOV R11,R17
RET
G90: LDI R17,$87 ;取浮点数90
MOV R8,R17
LDI R17,$34
MOV R9,R17
CLR R10
CLR R11
RET
DTOR: RCALL PI18 ;角度化为弧度
RJMP FPMU
RTOD: RCALL INVPI ;弧度化为角度
RJMP FPMU
GHPI: LDI R17,$81 ;取浮点数л/2
MOV R8,R17
LDI R17,$49
MOV R9,R17
LDI R17,$0f
MOV R10,R17
LDI R17,$DB
MOV R11,R17
RET
G01: LDI R17,$7D ;取浮点数0.1
MOV R8,R17
LDI R17,$4C
MOV R9,R17
LDI R17,$CC
MOV R10,R17
LDI R17,$CD
MOV R11,R17
RET
G1: LDI R17,$81 ;取浮点数1
MOV R8,R17
CLR R9
CLR R10
CLR R11
RET
PI18: LDI R17,$7B ;取浮点数л/180
MOV R8,R17
LDI R17,$0E
MOV R9,R17
LDI R17,$FA
MOV R10,R17
LDI R17,$35
MOV R11,R17
RET
GINT: LDI R17,R12 ;浮点数取整
CPI R17,$81
BRCC GINT1
RCALL G0 ;阶码<$81,结果为0
RJMP KP2
GINT1: ANDI R16,$DD
SBRC R13,7
ORI R16,2 ;记数符
CPI R17,$98
BRCC GOVER ;阶码>$97,溢出
RCALL BRK ;分解出整数部分(在R9 R10 R11)
SBRS R16,1
RET ;正数返回
NEG3A: COM R11 ;负数求(r9 r10 r11)之补
COM R10
COM R9
INC3A: LDI R17,255
SUBI R11,R17
SBCI R10,R17
SBCI R9,R17 ;求反后加1
RET
GOVER: ORI R16,$20 ;设整数部分超过23位标志
RET
BRK: ANDI R16,$DF ;将正浮点数分解为整数/小数两部分
LDI R17,$80
OR R13,R17 ;恢复尾数最高位
CLR R9
CLR R10
CLR R11
MOV R17,R12
SUBI R17,$80
BREQ BRKRT
BRCS LOOPT
CPI R17,$19 ;整数部分超过24位
BRCC GOVER ;为溢出
LOOP4: LSL R15
ROL R14
ROL R13
ROL R11
ROL R10
ROL R9
DEC R17
BRNE LOOPT ;左移位数为阶码-$80,整数部分进入r9-r11中
BRKRT: RET
LOOPT: LSR R13 ;只有小数部分右移尾数($80-阶码)位
ROR R14
ROR R15
INC R17
BRNE LOOPT
RET
NRML: ANDI R16,$BF ;1字节正整数(在R13中)规格化为浮点数
CLR R14
CLR R15
LDI R12,$88 ;设阶码
RJMP NMLOP
G10: LDI R17,$84 ;取浮点数10
MOV R8,R17
LDI R17,$20
MOV R9,R17
CLR R10
CLR R11
RET
GLN2: LDI R17,$80 ;取浮点数ln2(=0.6931471806)
MOV R8,R17
LDI R17,$31
MOV R9,R17
LDI R17,$72
MOV R10,R17
LDI R17,$18
MOV R11,R17
RET
GLN10: LDI R17,$82 ;取浮点数ln10(=2.302585093)
MOV R8,R17
LDI R17,$13
MOV R9,R17
LDI R17,$5D
MOV R10,R17
LDI R17,$8E
MOV R11,R17
RET
INVX: TST R12 ;计算1/X, X=0时溢出
BRNE INV
OV4: SEV
RET
INV: RCALL G1 ;取1
RJMP FPDI
;范例71 ;用荷纳法计算多项式值子程序
FPLN1: ORI R16,$10 ;设计算奇函数(lnx,sinx,arcsinx 等)标志
RCALL LD3 ;存X
RCALL KP2
RCALL FPMU ;计算X2
RJMP FLN0 ;
FPLN2: ANDI R16,$EF ;设计算偶函数(EXP,COSX等)标志
FLN0: RCALL LD1 ;存T,T=X 或T=X2
POP R30
POP R31 ;系数表数据地址进入Z
LSL R30
ROL R31 ;由按字取数变为按字节取数
LPM ;r0<--(z)取阶码
MOV R8,R0
ADIW R30,1 ;指针增1
LPM ;取尾数高位字节
MOV R9,R0
ADIW R30,1 ;z+1
LPM ;取尾数中位字节
MOV R10,R0
ADIW R30,1 ;z+1
LPM ;取尾数低位字节
MOV R11,R0 ;取浮点数到r8 r9 r10&r11
ADIW R30,1 ;z+1
PLN: RCALL M1 ;计算(....((An*T+A(n-1))*T+A(n-2))*T+....+Ai)*T
LPM
MOV R8,R0
ADIW R30,1
LPM
MOV R9,R0
ADIW R30,1
LPM
MOV R10,R0
ADIW R30,1
LPM
MOV R11,R0 ;取A(i-1)
ADIW R30,1
RCALL FPLAD ;计算(....((An*T+A(n-1))*T+A(n-2))*T+....+Ai)*T+A(i-1)
LPM
RCALL GET1
DEC R0
BRNE PLN ;1为停止符号;否则继续计算
PEND: SBRS R16,4
RJMP REND
RCALL GET3 ;奇函数 取出自变量
RCALL M1 ;自变量乘以计算结果才是函数值
REND: LSR R31
ROR R30 ;Z指针折半后
ADIW R30,1 ;增1为后继指令地址
IJMP ;转到该地址去执行
;范例72
LNX: TST R12 ;对数函数子程序
BREQ OV5
SBRS R13,7
RJMP LN1
OV5: SEV ;求负数或0的对数为错误
RET
LN1: ANDI R16,$7E ;R16,7:(T-1)/(T+1)或(2T-1)/(2T+1)之符号 R16,0:p之符号
; m
MOV R0,R12 ;设X=2 *T, 则LnX=m*Ln2+LnT,存入p=m
LDI R17,$F3
CP R15,R17
LDI R17,$04
CPC R14,R17
LDI R17,$35
CPC R13,R17 ; _
BRCC LN5 ;T>√2/2时跳转
DEC R0 ;取p=m-1 LnX=(m-1)*Ln2+LN(2T)
MOV R17,R15
OR R17,R14
OR R17,R13
MOV R12,R17
BREQ LN5A ;2T-1=0 只须计算(m-1)Ln2
RCALL KP2 ;R12 NOUSED!
LSL R9
ROL R10
ROL R11 ;(2T-1)
LSR R13
ROR R14
ROR R15
LDI R17,$80
OR R13,R17 ;2T+1
LDI R17,$7E
MOV R12,R17 ;取1/(2T+1)的阶码
RJMP LNTLP
LN5: ORI R16,$80 ;(T-1)为负,数符位改为1
RCALL KP2
RCALL NEG3A
LDI R17,$80
ADD R9,R17 ;计算(T-1)
LSR R13
ROR R14
ROR R15
LDI R17,$C0
OR R13,R17
LDI R17,$7F
MOV R12,R17 ;取1/(T+1)的阶码
LNTLP: LSL R11
ROL R10
ROL R9 ;(2T-1)或(T-1)规格化
DEC R12 ;调整(2T-1)/(2T+1))或(T-1)/(T+1)的阶码
SBRS R9,7
RJMP LNTLP
RCALL FPD3 ;计算(2T-1)/(2T+1)或(T-1)/(T+1) 位r16,7为商之数符
PUSH R0
RCALL FPLN1 ;计算LnT或Ln(2T)
.DB $7E,$12,$49,$25 ;0.14285714 ;er.total<0.000000029!
.DB $7E,$4C,$CC,$CD ;0.2
.DB $7F,$2A,$AA,$AB ;0.33333333
.DB $81,$00,$00,$00 ;1
.DB $01,$00 ;结束符
INC R12
POP R0
LN5A: LDI R17,$80
ADD R0,R17
BREQ LN53 ;p=$80结束
BRCS LN51
NEG R0
INC R16 ;p为负数
LN51: RCALL LD1 ;存LnT或Ln(2T)
MOV R13,R0
RCALL NRML ;|P|规格化
RCALL GLN2 ;取ln2
RCALL FPMU ;计算|p|*ln2
RCALL GET1 ;取LnT或Ln(2T)
SBRS R16,0
RJMP LN52
RCALL FPSU ;p<0 计算lnT-|p|*ln2或Ln(2T)-|p|*ln2
RET
LN52: RCALL FPAD ;p>0 计算lnT+|p|*ln2或Ln(2T)+|p|*ln2
LN53: RET
;范例73 ;对数衍生函数子程序
LGX: RCALL LNX ;计算lnx
RCALL GLN10 ;取ln10
RCALL EXCH
RJMP FPDI ;转计算lgx=lnx/ln10
LGAX: RCALL LD2 ;存a
RCALL EXCH
RCALL LNX ;计算lnx
RCALL GET2 ;取a
RCALL LD2 ;存lnx
RCALL EXCH
RCALL LNX ;计算lna
RCALL GET2 ;转计算logax=lnx/lna
RJMP FPDI
;范例74
EXP: MOV R17,R12 ;指数函数子程序
CPI R17,$68 ;X之阶<$68
E1: BRCC E2
RCALL G0
ROR R12 ;(R12)=$80
INC R12 ;取exp=1
RET
E2: ANDI R16,$3F ;r16,6:数符
SBRC R13,7
ORI R16,$40 ;负数
LDI R17,$7F
AND R13,R17 ;取正(取|X|)
LDI R17,$33
CP R15,R17
LDI R17,$0F
CPC R14,R17
LDI R17,$30
CPC R13,R17
LDI R17,$87
CPC R12,R17 ;|X|与88.02969 比较
BRCS E3 ;|X|<88.02969 转
SBRS R16,6
RJMP OV6
G0: CLR R12 ;若x<-88.02969
CLR R13
CLR R14 ;Exp=0
CLR R15
RET
OV6: SEV ;x>88.02969,Exp溢出
RET
E3: CLR R0 ;X整数部分予清除
LDI R17,$81
MOV R8,R17
LDI R17,$38
MOV R9,R17
LDI R17,$AA
MOV R10,R17
LDI R17,$3B
MOV R11,R17 ;取log2e(=1/ln2)
RCALL FPMU ;计算X/ln2
LDI R17,$80
SBRC R16,6
OR R13,R17
MOV R17,R12
CPI R17,$81
BRCS E6 ;X/ln2整数部分为0 转
RCALL BRK ;否则分解该数为整数I(在R11),小数F两部分
LDI R17,$80
MOV R12,R17
RCALL NOM ;小数部分规格化为浮点数
SBRC R16,6
NEG R11 ;整数部分求补
MOV R0,R11 ;
E6: PUSH R0
RCALL FPLN2 ;计算EXP(F*ln2)
.DB $69,$5A,$92,$9F ;0.10178086 E-6 ;er.total<0.000000024
.DB $6D,$31,$60,$11 ;0.13215487 E-5
.DB $70,$7F,$E5,$FE ;0.15252734 E-4
.DB $74,$21,$84,$89 ;0.15403530 E-3
.DB $77,$2E,$C3,$FF ;0.13333558 E-2
.DB $7A,$1D,$95,$5B ;0.96181291 E-2
.DB $7C,$63,$58,$47 ;0.55504109 E-1
.DB $7E,$75,$FD,$F0 ;0.24022651
.DB $80,$31,$72,$18 ;0.69314718
.DB $81,$00,$00,$00 ;1
.DB $01,$00 ;结束符
POP R0
ADD R12,R0 ;整数部分I 加入阶码中
RET
;范例75 ;指数衍生函数子程序
DXP: RCALL GLN10 ;取ln10
RJMP EXP0 ;转计算EXP(X*ln10)
AXP: RCALL LD2 ;存X
RCALL EXCH
RCALL LNX ;计算lna
RCALL GET2 ;取出x
EXP0: RCALL FPMU
RJMP EXP ;转计算EXP(X*lna)
;范例76 ;双曲函数和反双曲函数子程序
SHX: RCALL SUB11 ;计算双曲正弦
RCALL FPSU
BRNE NX48
RET
CHX: RCALL SUB11 ;计算双曲余弦
RCALL FPAD
NX48: DEC R12
RET
SUB11: RCALL EXP
RCALL LD2
RCALL INVX
RJMP GET2
ASHX: RCALL SUB2 ;计算反双曲正弦
RCALL FPAD
ASH: RCALL FPSQ
RCALL GET2
RCALL FPAD
RJMP LNX
ACHX: RCALL SUB2 ;计算反双曲余弦
RCALL EXCH
RCALL FPSU
RJMP ASH
SUB2: RCALL LD2 ;存X
RCALL KP2
RCALL FPMU ;得到X2
RJMP G1 ;取浮点数1
; 范例77 ;正弦函数子程序
SINX: RCALL RTOD ;弧度化为角度
SINX1: CLR R16 ;X1为角度
SBRC R13,7
INC R16 ;存数符
LDI R17,$7F ;X1-->|X1|
AND R13,R17
NX30: RCALL G90
INC R8
INC R8 ;取360°
RCALL FPCP1 ;|X1|与360°比较
BREQ GE0 ;相等,转出
BRCC NX31 ;|X1|<360° 转出
RCALL EXCH
RCALL FPSU ;否则|X1|-360°-->|X1|
RJMP NX30 ;循环
NX31: DEC R8
RCALL FPCP1 ;|X1|与180°比较
BREQ GE0 ;相等,转出
BRCC NX32 ;|X1|<180°,转
RCALL EXCH
RCALL FPSU ;否则|X1|-180°-->|X1|
INC R16 ;将数符求反
NX32: RCALL G90
RCALL FPCP1 ;|X1|与90°比较
BRCC NX36
INC R8
RCALL FPSU ;|X1|>90°,取180°-|x1|-->|x1|
RJMP NX36
GE0: RJMP G0 ;|X1|=0 则sinX=0
NX36: RCALL DTOR ;变回弧度X
MOV R17,R12
CPI R17,$79 ;阶码<$79,sinX=X
BRCS PP2
RCALL FPLN1 ;计算sin|X|
.DB $60,$30,$92,$32 ; 0.16059044 E-9 er.total<0.0000000071
.DB $67,$D7,$32,$2A ;-0.25052108 E-7
.DB $6E,$38,$EF,$1C ; 0.27557319 E-5
.DB $74,$D0,$0D,$01 ;-0.19841270 E-3
.DB $7A,$08,$88,$88 ; 0.83333333 E-2
.DB $7E,$AA,$AA,$AA ;-0.16666667
.DB $81,$00,$00,$00 ;1
.DB $01,$00 ;结束符
PP2: LDI R17,$80
SBRC R16,0
PP3: OR R13,R17 ;配置数符
DON6: RET
;范例78 ;衍生三角函数子程序
CTNX: RCALL RTOD ;弧度化为角度
CTNX1: RCALL TANX1 ;计算tgX
RJMP INVX ;取倒数为ctgX
TANX: RCALL RTOD ;弧度化为角度
TANX1: RCALL LD2 ;存X
RCALL SINX1 ;计算sinX
RCALL GET2 ;取X
RCALL LD2 ;存sinX
RCALL EXCH
RCALL COSX1 ;计算cosX
BRNE NX39
OV7: SEV
RET ;cosX=0,溢出
NX39: RCALL GET2 ;取sinX
RJMP FPDI ;tgX=sinX/cosX
COSX: RCALL RTOD ;弧度化为角度
COSX1: RCALL G90 ;取浮点数90°
RCALL FPSU
RJMP SINX1 ;cosX=sin(90-X)
;范例79 ;反正弦函数子程序
ASINX: MOV R17,R12
CPI R17,$78
BRCS DON6 ;X阶码<$78,acrsinX=X
ANDI R16,8 ;清除数符和|X|>0.5标志,保留计算acosx标志(R16,3)
SBRC R13,7
INC R16 ;记数符
LDI R17,$7F
AND R13,R17 ;取绝对值 X-->|X|
RCALL G1
RCALL FPCP1
BREQ AA
BRCC AA1
OV8: SEV ;|X>1,溢出
RET
AA: RCALL GHPI
RCALL EXCH
RJMP PP2 ;|X|=1,arcsinX=±л/2
AA1: MOV R17,R12
CPI R17,$80
BRNE AS1 ;|X|<0.5,y=|x|
MOV R17,R13
OR R17,R14
OR R17,R15
BREQ AS1 ;X=0.5,y=|x|
ORI R16,$20 ;X>0.5,建标
RCALL NEG3
LDI R17,$80
ADD R13,R17
LDI R17,$7F
MOV R12,R17 ;((1-|x|)/2)方根之阶最大为$7F
NRMLP: LSL R15
ROL R14
ROL R13
DEC R12
SBRS R13,7
RJMP NRMLP ; __________
RCALL FPS0 ;√(1-|X|)/2-->y
AS1: RCALL FPLN1 ;计算arcsiny
.DB $7A,$3D,$43,$C4 ;0.11551801 E-1 er. total<0.0000000245
.DB $7A,$64,$CC,$CD ;0.13964844 E-1
.DB $7B,$0E,$27,$62 ;0.17352764 E-1
.DB $7B,$37,$45,$D1 ;0.22372159 E-1
.DB $7B,$78,$E3,$8E ;0.30381944 E-1
.DB $7C,$36,$DB,$6E ;0.44642857 E-1
.DB $7D,$19,$99,$9A ;0.075
.DB $7E,$2A,$AA,$AA ;0.16666667
.DB $81,$00,$00,$00 ;1
.DB $01,$00 ;结束符
SBRS R16,5
RJMP PP2 ;|x|≤0.5 转配置数符,有acsin|x|=acsiny
INC R12 ;否则取2arcsiny(=arccosx)
SBRC R16,3 ;测试计算ARCCOSX的标志
RJMP ACSRT ;有计算ARCCOSX标志,转清除该标志(其余计算在ACOSX子程序中完成)
RCALL GHPI ;否则取л/2
AS2: RCALL FPSU ;|X|>0.5时,arcsin|X|=л/2 -2arcsiny
PP20: RJMP PP2 ;转去配置数符
;范例80 ;函数值为弧度的反三角函数子程序
ACOSX: ORI R16,8 ;设计算arccosx标志
RCALL ASINX ;调反正弦函数子程序
RCALL GHPI ;取л/2
SBRC R16,3 ;计算ARCCOS|X|标志未被清除?
RJMP AS3 ;是,转计算arccosx=л/2-arcsinx
SBRS R16,0 ;x>0且x>0.5
RJMP ACSRT ;有arccosx=2arcsiny!
INC R8 ;否则取л;即当x<0且|X|>0.5时,有arccosX=л-2arcsiny
AS3: RCALL FPSU
ACSRT: ANDI R16,$F7 ;清除计算arccosx标志
RET
ATANX: MOV R17,R12 ;反正切函数子程序
CPI R17,$98
BRCS AT1
RCALL GHPI ;X阶码大于$98,取л/2
RCALL EXCH
ROL R9
BRCC AT2
LDI R17,$80
OR R13,R17 ;arctgx=л/2
AT2: RET
AT1: MOV R17,R12
CPI R17,$74 ;X阶码小于$74,arctgX=X
BRCS AT2
RCALL KP2
RCALL LD1 ;存X
RCALL FPMU
RCALL G1
RCALL FPAD ; _______
RCALL FPSQ ;计算 √(1+X2)
RCALL GET1
RCALL FPDI
RJMP ASINX ;转计算arctgx=arcsin(X/√<(1+X2)
ACTNX: RCALL ATANX ;反余切函数子程序
RCALL GHPI
RJMP FPSU ;arcctgX=л/2-arctgx
;范例81 ;函数值为角度的反三角函数子程序
ASNX: RCALL ASINX ;反正弦函数子程序,结果以角度表示
RJMP RTOD
ACSX: RCALL ACOSX ;反余弦函数子程序,结果以角度表示
RJMP RTOD
ATNX: RCALL ATANX ;反正切函数子程序,结果以角度表示
RJMP RTOD
ACNX: RCALL ACTNX ;反余切函数子程序,结果以角度表示
RJMP RTOD
;范例82 ;函数计算子程序演示程序
DMST2: LDI R16,2
OUT SPH,R16
LDI R16,$5F ;堆栈指针初始化
OUT SPL,R16
LDS R11,$65 ;取操作数(自变量X)
LDS R12,$66 ;r11,7:数符 r11,6:阶符
LDS R13,$67 ;r11,5--0:阶(最大为38)
LDS R14,$68
LDS R15,$69 ;r12-r15:十进制尾数(8位BCD码)
RCALL DTOB ;翻为二进制浮点数
RCALL LNX ;调函数子程序之一
RCALL BTOD ;将函数值转为十进制浮点数
DMHER: RJMP DMHER
;范例83 ;阶乘子程序
NP: RCALL G1 ;取浮点数1
MOV R17,R12 ;二进制整数N在R12中
CPI R17,2 ;N<2,N!=1
BRCS GG
CPI R17,34
BRCS NX59
OV9: SEV ;N>33,溢出
RET
GG: RJMP SAV0 ;取N!=1
NX59: MOV R0,R12 ;存N
DEC R0 ;N-1
LDI R17,1
PUSH R17 ;取T=1 并存入
LDI R17,$81
STS $70,R17
CLR R17
STS $71,R17
STS $72,R17
STS $73,R17 ;存储浮点数1
L43: POP R13 ;取T
INC R13 ;T+1-->T
PUSH R13 ;存T
RCALL NRML ;T规格化
RCALL GET1 ;取阶段阶乘结果
RCALL FPMU ;得到当前T!
RCALL LD1
DEC R0
BRNE L43 ;T=N时得到N!
POP R0
RET
;范例84 ;长整数(r9,r10,r11,r12)规格化为浮点数
LINOM: BST R9,7 ;数符存于T
BRTC LI10
CLR R16 ;负数求补
SUB R16,R12
MOV R12,R16
CLR R16
SBC R16,R11
MOV R11,R16
CLR R16
SBC R16,R10
MOV R10,R16
CLR R16
SUB R16,R9
MOV R9,R16
LI10: LDI R16,$A0 ;取阶32(长整数共32位)
LP10: SBRC R9,7
RJMP NX63 ;最高位为1,已规格化
LSL R12
ROL R11
ROL R10
ROL R9 ;否则左规1位
DEC R16 ;阶码减1
CPI R16,$80
BRNE LP10
RJMP G0 ;左规达32次,浮点数为0
;范例85 ;定点十进制数翻为二进制浮点数
DTOB1: RCALL LD1 ;存入十进制小数
RCALL CONV2 ;定点整数十翻二
RCALL GET1 ;取出十进制小数
RCALL LD1
RCALL SAV0
RCALL CONV4 ;定点小数十翻二
RCALL GET1 ;取出二进制定点整数
LDI R16,$98 ;予设阶码
LP11: SBRC R9,7
RJMP NX63 ;最高位为1,已规格化
LSL R15
ROL R14
ROL R13
ROL R12
ROL R11
ROL R10
ROL R9 ;整数和小数部分左移一位
DEC R16 ;阶码减1
CPI R16,$60
BRNE LP11
RET ;得到浮点数0
NX63: MOV R13,R9
MOV R14,R10
MOV R15,R11 ;尾数取到r13-r15
SBRS R12,7
RJMP PP6
RCALL INC3 ;尾数截去部分舍入
BRNE PP6
INC R16 ;尾数变为0将阶码增1
SEC
ROR R13 ;$80-->r13,即将尾数变为0.5
PP6: MOV R12,R16 ;取回阶码
BLD R13,7 ;装入数符(T-->R13,7)
RET
;范例86 ;浮点数十翻二
DTOB: ANDI R16,$FC ;r11,7:数符 r11,6:阶符 r11,5--0:阶(最大为38)
SBRC R11,6 ;R12---R15;8BCD码尾数
INC R16 ;阶符存于R16,0
SBRC R11,7
ORI R16,2 ;数符存于R16,1
MOV R17,R11
ANDI R17,$3F ;取阶
MOV R0,R17 ;存于R0
MOV R8,R12
OR R8,R13
OR R8,R14
OR R8,R15
BREQ PP8 ;十进制浮点数尾数为0,取二进制浮点数0
PUSH R16
RCALL CONV4 ;十进制浮点数尾数翻为二进制定点小数
MOV R16,R15
MOV R15,R14
MOV R14,R13
MOV R13,R12 ;二进制定点小数转入r13r14r15r16
LDI R17,$80 ;予设阶码
MOV R12,R17
LP14: SBRC R13,7
RJMP NX67
LSL R16
ROL R15
ROL R14
ROL R13
DEC R12
RJMP LP14 ;二进制定点小数规格化为浮点数
NX67: SBRS R16,7
RJMP NX66
RCALL INC3 ;调整
BRNE NX66
INC R12
SEC ;调整后结果为0将其改为0.5
ROR R13 ;即$80-->r13
NX66: LDI R17,$7F
POP R16
SBRS R16,1
AND R13,R17 ;配置数符
SBRS R16,0
RJMP DBL4 ;正阶转
DBL1: LDI R17,$10
SUB R0,R17
BRCS DBL2
RCALL INVDP ;
RCALL FPMU ;阶码减10, X*10ˉ1o -->X
RJMP DBL1
DBL2: ADD R0,R17 ;不够减则恢复阶
BREQ PP8
DBL3: RCALL G01 ;取0.1
RCALL FPMU
DEC R0 ;X*0.1-->X,阶减1
BRNE DBl3
RET
DBL4: LDI R17,$10
SUB R0,R17 ;阶减10
BRCS DBL5
RCALL DDP ;X*101o -->X
RCALL FPMU
RJMP DBL4
DBL5: ADD R0,R17 ;不够减则恢复阶
BREQ PP8
DBL6: RCALL G10
RCALL FPMU
DEC R0 ;X*10-->X,阶减1
BRNE BDL6
PP8: RET
INVDP: LDI R17,$5F ;取浮点数10ˉ1o
MOV R8,R17
LDI R17,$5B
MOV R9,R17
LDI R17,$E6
MOV R10,R17
LDI R17,$FF
MOV R11,R17
RET ;
DDP: LDI R17,$A2 ;取浮点数101o
MOV R8,R17
LDI R17,$15
MOV R9,R17
LDI R17,$02
MOV R10,R17
LDI R17,$F9
MOV R11,R17
RET
;范例87 ;浮点数二翻十
BTOD: TST R12
BREQ PP4 ;转取十进制浮点数0
ANDI R16,$FC ;予清十进制浮点数数符及阶符(r16,1&0)
CLR R0 ;予清十进制浮点数之阶
SBRC R13,7
ORI R16,2 ;取数符
LDI R17,$7F
AND R13,R17 ;取绝对值
BTA: RCALL DDP
RCALL FPCP1 ;|X|与101o 比较
BREQ BTB
BRCC BTC ;|X|<101o 转
BTB: RCALL INVDP
RCALL FPMU ;|X|*10ˉ1o-->|X|
LDI R17,$10
ADD R0,R17 ;十进制浮点数阶加10
RJMP BTA
BTC: RCALL INVDP ;
RCALL FPCP1 ;|X|与10ˉ1o 比较
BREQ BTC1 ;
BRCS BT0 ;|X|>10ˉ1o 转
BTE: RCALL DDP ;
RCALL FPMU ;|X|*101o -->|X|
LDI R17,$10
ADD R0,R17 ;十进制浮点数阶加10
ORI R16,1 ;置负阶
RJMP BTC
BTC1: LDI R17,9 ;|X|=10ˉ1o 特别处理
ADD R0,R17 ; -9
ORI R16,1 ;取0.1*10
SJMP BT4
BT0: RCALL G1
RCALL FPCP1 ;|X|与1比较
BREQ BT1
BRCC BT2 ;|X|<1转
BT1: RCALL G01
RCALL FPMU ;|X|*0.1-->|X|
INC R0 ;十进制浮点数阶加1
RJMP BT0
BT2: RCALL G01
RCALL FPCP1 ;|X|与0.1比较
BREQ BT4
BRCS BDS ;|X|≤0.1转出
BT3: RCALL G10
RCALL FPMU ;|X|*10--->|X|
INC R0 ;十进制浮点数阶加1
ORI R16,1 ;置负阶
RJMP BT2
PP4: RJMP KP2 ;十进制浮点数取为0
BT4: LDI R17,$10
MOV R9,R17
CLR R10
CLR R11
CLR R12 ;十进制浮点数尾数取为0.10000000
BT6: MOV R8,R0 ;取十进制浮点数阶
SBRS R8,3
RJMP BT7
SBRC R8,1
SUBI R8,$FA ;对产生非法BCD调整(加6)
BT7: LDI R17,$40
SBRC R16,0
OR R8,R17 ;配置阶符(r8,6)
LSL R17
SBRC R16,1
OR R8,R17 ;配置阶浮(r8,7)
RET
BDS: RCALL BT6 ;BT6将十进制浮点数阶,阶符和数符配置到R8
LDI R17,$80
OR R13,R17 ;恢复尾数最高位
LDI R17,$98
SUB R17,R12 ;右移次数为($98-阶码)
RJMP CONV31 ;调CONV31子程序完成尾数二翻十,结果在(r9r10r11r12)
;范例88 ;二进制浮点数快速翻为定点十进制数,整数在r9,r10,r11中,小数在r13,r14,r15中
FBTOD: RCALL BRK ;二进制浮点数分解为整数和小数两部分
SBRC R16,5
RET ;整数部分多于24位,溢出
MOV R0,R13
MOV R5,R14
MOV R8,R15 ;小数部分转入R0R5R8
RCALL CONV1 ;定点整数二翻十,结果在R12,R13,R14,R15
RCALL LD1 ;十进制整数-->RAM
MOV R15,R8
MOV R14,R5
MOV R13,R0 ;取回二进制小数
RCALL CONV3 ;定点小数二翻十,结果在r9,r10,r11,r12
RCALL EXCH1 ;十进制定点小数转入r13,r14,r15,r12
RCALL GET1 ;取出十进制定点整数r8,r9,r10,r11)/小数在r13,r14,r15,r12
CLR R16 ;清除无用的标志!
RET
;范例89
.ORG $E80 ;最小二乘法拟和直线子程序
.EQU NUMB=10 ;取10点,即十对浮点数,按增地址存放Y1,X1,Y2,X2,..Yn,Xn
.EQU TABLA=$9000 ;数据表,第一个浮点数为Y1
STRT: LDI R28,$70
CLR R29 ;POINT TO $0070
LP51: ST Y+,R29 ;累加和或暂存区清除(LD1,LD2,LD3,LD4和LD5子程序工作区)
CPI R28,$84
BRNE LP51
LDI R16,NUMB ;取拟合点数
MOV R0,R16
LDI R29,$90
CLR R28 ;参加拟合数据首地址$9000
IN R16,MCUCR,7
SBR R16,$C0 ;片外RAM,选一个读写等待周期
OUT MCUCR,R16
LOOP3: RCALL GETA ;取浮点数Yi 占4字节 即Yi0,Yi1,Yi2,Yi3
RCALL INVX ;计算1/Yi
RCALL LD6 ;暂存
RCALL GET1 ;取累加和 n
RCALL FPAD ;1/Yi加入累加和(∑1/Yi是 ∑1/Yi 简写形式,下同)
RCALL LD1 ; i=1
RCALL GET6 ;取1/Yi
PUSH R28
PUSH R29 ;保护堆栈指针
RCALL GETA ;取浮点数Xi(Xi0,Xi1,Xi2,Xi3)占4字节
POP R29
POP R28 ;恢复堆栈指针,仍指向Xi
RCALL FPMU ;计算Xi/Yi
RCALL LD7 ;暂存
RCALL GET2
RCALL FPAD ;Xi/Yi加入累加和∑(Xi/Yi)
RCALL LD2
RCALL GET7 ;取出Xi/Yi
RCALL SAV0 ;
RCALL FPMU ;计算(Xi/Yi)2
RCALL GET3
RCALL FPAD ;(Xi/Yi)2加入累加和∑(Xi/Yi)2
RCALL LD3
RCALL GET6 ;取1/Yi
RCALL SAV0 ;
RCALL FPMU ;计算1/Yi2
RCALL LD6 ;暂存
RCALL GET4
RCALL FPAD ;1/Yi2 加入累加和∑1/Yi2
RCALL LD4
RCALL GET6 ;取出1/Yi2
RCALL GETA ;再取Xi
RCALL FPMU ;计算Xi/Yi2
RCALL GET5
RCALL FPAD ;Xi/Yi2加入累加和∑Xi/Yi2
RCALL LD5
DEC R0 ;点数减1
BRNE LOOP3 ;未到总点数n,循环
RCALL GET4
RCALL SAV0
RCALL GET3
RCALL FPMU ;计算(∑1/Yi2)*(∑(Xi/Yi)2) 并存入
RCALL LD6
RCALL GET5 ;取出∑Xi/Yi2
RCALL SAV0
RCALL FPMU ;计算(∑Xi/Yi2)2
RCALL GET6
RCALL FPSU ;计算c=(∑1/Yi2)*(∑(Xi/Yi)2-(∑Xi/Yi2)2
RCALL LD6 ;存入
RCALL GET2
RCALL SAV0
RCALL GET4
RCALL FPMU ;计算(∑(Xi/Yi)*(∑1/Yi2)并存入
RCALL LD7
RCALL GET1
RCALL SAV0
RCALL GET5
RCALL FPMU ;计算(∑1/Yi)*(∑(Xi/Yi2) 并存入
RCALL GET7
RCALL FPSU ;计算d=(∑Xi/Yi)*(∑1/Yi2)-(∑1/Yi)*(∑Xi/Yi2))
RCALL GET6 ;取c
RCALL EXCH
RCALL FPDI ;计算b=d/c并存入
RCALL LD7
RCALL GET5 ;取 ∑Xi/Yi2
RCALL FPMU ;计算(∑Xi/Yi2)*b
RCALL GET1
RCALL FPSU ;计算(∑1/Yi)-(∑Xi/Yi2)*b
RCALL GET4 ;取 ∑1/Yi2
RCALL EXCH
RCALL FPDI ;计算a=(∑1/Yi-(∑Xi/Yi2)*b)/∑1/Yi2
RCALL LD6 ;结果a在$84-$87中,b在$88-$8b中
RER
GETA: LD R12,Y+
LD R13,Y+
LD R14,Y+
LD R15,Y+ ;从外部SRAM中取浮点数到R12-R15
RET
LD4: STS $7C,R12 ;存浮点数
STS $7D,R13
STS $7E,R14
STS $7F,R15
RET
LD5: STS $80,R12 ;计算∑Xi/Yi2的存储单元
STS $81,R13
STS $82,R14
STS $83,R15
RET
LD6: STS $84,R12 ;暂存1/Yi,1/Yi2等浮点数
STS $85,R13
STS $86,R14
STS $87,R15
RET
LD7: STS $88,R12 ;暂存Xi/Yi等浮点数
STS $89,R13
STS $8A,R14
STS $8B,R15
RET
GET4: LDS R8,$7C ;取浮点数
LDS R9,$7D
LDS R10,$7E
LDS R11,$7F
RET ;
GET5: LDS R8,$80 ;取∑Xi/Yi2或中间结果
LDS R9,$81
LDS R10,$82
LDS R11,$83
RET
GET6: LDS R8,$84 ;取浮点数1/Yi,1/Yi2等
LDS R9,$85
LDS R10,$86
LDS R11,$87
RET
GET7: LDS R8,$88 ;取浮点数Xi/Yi等
LDS R9,$89
LDS R10,$8A
LDS R11,$8B
RET
;范例90
GETAD: LDI R17,0Bxxx01110;PC0&PC4输入/PC1-PC3输出&PC3(CAL)
OUT DDRC,R17 ;
CBI PORTC,1
GAD1: SBI PORTC,4
SBIB PINC,4 ;查DRDY
RJMP GAD1 ;低为数据准备好
GAD2: SBI PORTC,4
SBIC PINC,4 ;PINC:$13/PORTB:$15
RJMP GAD2 ;DRDY低有效
CBI PORTC,2 ;置片选有效
LDI R16,16 ;16位数据
GETL0: CLC ;予清除C
SBI PORTC,0
SBIC PINC,0 ;接收一位数据
SEC
ROL R14 ;数据高位在前
ROL R13 ;在R13R14里带进位左移
SBI PORTC,1
CBI PORTC,1 ;发出时钟,下降沿读出数据
DEC R16
BRNE GETL0
SBI PORTC,2 ;置片选无效
MOV R4,R14 ;
MOV R3,R13 ;保存
GADCOM: CLR R15 ;3字节小数r13r14r15(0)规格化为浮点数
LDI R16,$80
MOV R12,R16 ;阶码为$80
GAD: SBRC R13,7
RJMP GETL2
LSL R14
ROL R13 ;尾数左移,阶码递减
DEC R12
BRNE GAD
RET ;如果(r12)=0 得到0浮点数
GETL2: LDI R16,$7F
AND R13,R16 ;正数
LDI R16,$82 ;取浮点数2.5(基准源为2.5v)
MOV R8,R16
LDI R16,$20
MOV R9,R16
CLR R10
CLR R11
RCALL FPMU ;相乘
RET ;(r12)不为0
; 以下提供几个补充参考程序,都带有详细说明和指令注释.它们是主从多机通讯程序,采
;用中断方式写入EEPROM,直接对晶振分频产生0.1秒和秒号的精确定时程序,以及RS-232/
;RS-485标准转换程序,AVR频率计程序,串行时锺日历芯片DS1302读写,共享时基的PWM输出、
;输入捕获测周期程序和定时信号获取,以及DS18B20测温等程序.多机通讯主要用8和9位数
;据模式区分被选分机(9位)和其它分机(8位),达到主机只与被选分机交换数据之目的.以中
;断方式写EEPROM的优点是可与系统运行同时进行(即在线写入),占用很少机时.
;精确定时用定时/计数器1(或0)直接对MCU主频(不设分频)设定时间常数,分频精度可达到
;1HZ.RS-232/RS-485标准转换程序中AVR不作中转,使两种器件相关脚位直接连接.以TCNT0
;定时,以T0引脚接收RS-232数据.以收到RS-232字符起始位下跳沿或结束符($03)为依据,
;控制切换RS-485的收发使能.(系统中的主AVR可兼做对通信标准之监控转换,即只是在完成
;主要工作任务的同时'附带'进行).具体过程不再细述.串行时锺日历芯片DS1302具体积小,
;可靠性高,与单片机连接方便等优点.
; 以下程序请参看有关章节或程序中的注释。
;范例91 ;多机通讯主机程序/晶振4MHZ
.ORG 0 ;以8/9位数据模式区分被选/未被选分机通讯
.EQU DTPINT=$180 ;UBRR=12 波特率19200(REL.ERR.=0.16%)
.EQU DRPINT=$1C0 ;主机对1#,2#,3#,4#分机发送数据块在$180-18F,$190-19F,$1A0-1AF)和$1B0-1BF
STRT38: RJMP RST38 ;主机从1#,2#,3#,4#分机接收数据块在$1C0-1CF,$1D0-1DF,$1E0-1EF)和$1F0-1FF
.ORG $00B ;
RJMP STRT38
.ORG $00C
RJMP STRT38 ;主机不设串口中断,只以查询接收
.ORG $011
RST38: LDI R16,12
OUT UBRR,R16 ;设波特率:[BAUD RATE=FCP/16(UBRR+1)]
CLR R15 ;初始化分机号
LDI R27,HIGH(DTPINT)
LDI R26,LOW(DTPINT);发送数据指针(首指$180)
LDI R29,HIGH(DRPINT)
LDI R28,LOW(DRPINT);接收数据指针(首指$1C0)
NEXTNO: LDI R16,$18
OUT UCR,R16 ;允许UART接收和发送,8位数据模式
INC R15 ;指向1#分机
OUTLP: OUT UDR,R15 ;呼分机号,1:1#/2:2#/03:3#/04:4#...
TSLOP: IN R16,USR
SBRS R16,7
RJMP TSLOP ;分机返回机号?
IN R16,UDR
CP R16,R15 ;分机号正确返回?
BRNE OUTLP
LDI R16,$1C ;改为9位数据模式 TXB8=0
OUT UCR,R16 ;
TXLOP: LD R16,X+
OUT UDR,R16 ;向分机发送数据块
TESTL: IN R17,USR
SBRS R17,5
RJMP TESTL ;等待发送完成
CPI R16,$0A
BRNE TXLOP ;
RXTST: IN R17,USR
SBRS R17,7 ;RXC=1 收到数据
RJMP RXTST ;等待接收分机返回数据块
IN R16,UDR
ST Y+,R16 ;存储接收数据
CPI R16,$0A ;分机数据块发完?
BRNE RXTST
MOV R16,R15
CPI R16,4 ;与分机轮询通讯完毕?
BRNE NEXTNO ;未完转对下一分机通信
HH38: RJMP HH38 ;否则踏步(可改为处理分机返回的数据,之后再进行下一个轮询)
.DSEG
.ORG $180
DTPINT:.BYTE $40
;$41 $45 $65 $73 $46 $42 $40 $6F $33 $44 $66 $5C $4D $4B $0D $0A
;$42 $4F $66 $78 $47 $45 $44 $63 $32 $48 $60 $7C $6D $45 $0D $0A
;$43 $56 $55 $53 $4D $4F $40 $2E $31 $42 $67 $4C $47 $4A $0D $0A
;$45 $54 $59 $63 $3D $4B $48 $2F $35 $48 $69 $3C $77 $43 $0D $0A
.ORG $1C0
DRPINT:.BYTE $40
;范例92
.ORG 0 ;多机通讯1#分机程序/晶振4MHZ
.EQU DTPIT1=$180 ;(UBRR)=12 波特率为19200(REL.ERR.=0.16%)
.EQU DRPNT1=$1C0
STRT39: RJMP RST39
.ORG $00B
RJMP UARXC ;8535UART接收完成中断
.ORG $00C
RJMP UATXC ;UART发送完成中断
.ORG $011
RST39: CLR R18 ;清除分机被选中(R18,6)和主机数据块接收完毕标志(R18,7)
LDI R16,12
OUT UBRR,R16 ;设波特率[BAUD RATE=4000000/16*(12+1)=19200]
LDI R16,HIGH(DRPNT1)
MOV R8,R16
LDI R16,LOW(DRPNT1)
MOV R9,R16 ;r8,r9:接收数据指针(FIRST POINT TO $1C0)
LDI R16,$98 ;允许UART中断接收,8位数据模式
OUT UCR,R16
SEI
RXDTS: SBRS R18,6 ;主机呼号已收到(若收到,在R17中)?
RJMP RXDTS
OUT UDR,R17 ;返还该机号
TXDON: IN R16,USR
SBRS R16,5
RJMP TXDON ;该机号发送完成?
LDI R16,$9C ; 允许UART中断接收,9位数据模式,TXB8=0
OUT UCR,R16
RCVBLK: SBRS R18,7
RJMP RCVBLK ;主机发来数据块已接收完毕?
LDI R16,HIGH(DTPIT1)
MOV R6,R16
LDI R16,LOW(DTPIT1)
MOV R7,R16 ;设发送数据指针r6r7,首指$180
LDI R16,$3C ;允许UART中断发送,9位数据模式,TXB8=0
OUT UCR,R16
TXDN: SBIC UCR,5
RJMP TXDN ;发送完毕?
RJMP RST39 ;
:UART中断接收程序
UARXC: SBIC USR,4
RETI ;祯错误(主机正与其它分机进行9位数据模式通信),不予接收
IN R14,SREG ;保存当前状态
TST R18
BREQ NUMB ;(R18)=0时收到数据,只可能是机号,转去核实
PUSH R16 ;否则为主机向本分机发来数据块(9位模式,机号已符合)
PUSH R26
PUSH R27
IN R17,UDR ;接收数据
MOV XH,R8
MOV XL,R9 ;取接收数据指针
ST X+,R17 ;转入RAM
MOV R8,XH
MOV R9,XL ;存数据指针
CPI R17,$0A ;是数据块结束符LF?
BRNE RSCOM1
SBR R18,$80 ;收到完整数据块标志
RSCOM1: POP R27
POP R26
POP R16
DRETI: OUT SREG,R14
RETI
NUMB: IN R17,UDR ;取出数据
CPI R17,1 ;是1#分机?2#分机与$02比较/3#分机与$03比较...
BRNE DRETI ;机号不符合,转!
SBR R18,$40 ;建机号符合标志
RJMP DRETI
; UART中断发送程序
UATXC: PUSH R16 ;r6 r7:发送数据指针,首指$180
IN R16,SREG
PUSH R16
PUSH R26
PUSH R27
MOV XH,R6
MOV XL,R7 ;取出发送指针
LD R16,X+ ;取数据,调指针
MOV R6,XH
MOV R7,XL
OUT UDR,R16 ;送入发送寄存器
CPI R16,$0A
BRNE SDCOM
CBI UCR,5 ;发送最后1个字符后,禁止发送寄存器空中断(CLR UDRIE)
LDI R16,HIGH(DRPINT)
MOV R8,R16
LDI R16,LOW(DRPINT)
MOV R9,R16 ;接收数据指针初始化(POINT TO $1C0)
SDCOM: POP R27
POP R26
POP R16
OUT SREG,R16
POP R16
RETI
.DSEG
.ORG $180
DTPIT1:.BYTE $40
.ORG $1C0
DRPNT1:.BYTE $10
;$41 $45 $65 $73 $46 $42 $40 $6F $33 $44 $66 $5C $4D $4B $0D $0A
;范例93
;以中断方式写入EEPROM(仅对8535,8515无此功能),克服查询方式占用过多机时的缺点,
;并可在线写入
;运作过程特点如下:
;(1)主程序初始化时设置EEPROM就绪(ready)中断使能位和中断总使能位
;(2)在主程序中写入第一个字节,写入完成后引起就绪中断,其他写入在中断服务中完成
;(3)本程序为一写入特例,写入地址为$100--$1FF,可作适当修改(如设块长计数器等)
;(4)为防止高优先级中断破坏写入过程,中断服务中不允许中断嵌套
;(5)本例为简化程序只以查询写入地址循环作为背景程序,实用时可改为具体的背景序
;(6)如能确信当前系统没有EEPROM正在写入,可删除对其进行查询部分.
STWEEP: LDI R16,HIGH(ramend)
OUT SPH,R16
LDI R16,LOW(ramend)
OUT SPL,R16
SBI EECR,3 ;设置EEPROM就绪(ready)中断使能位
SEI ;中断总使能
RJMP SRTW
.ORG $00F
RJMP EEPRDY ;8535 EEPROM就绪(ready)中断向量
SRTW: LDI YH,1
LDI YL 0 ;EEPROM 写入首地址:$100
LDI XL,$60 ;欲写入数据块首地址:$60
CLR XH
WEEP0: SBIC EECR,1 ;当前有EEPROM写入操作,有则等待写入完成
RJMP WEEP0
RCALL WREEP ;写入第一个字节,($60)->$100,写入完成后,EEWE=0时引发EEPROM就绪中断
INC YL ;调整写入地址指针
HHWEEP: TST YL
BRNE HHWEEP
CPI YH,2 ;写入地址达到$200后,写入完成
BRNE HHWEEP
CBI EECR,3 ;禁止EEPROM就绪(ready)中断
WDON: RJMP WDON ;踏步
EEPRDY: IN R6,SREG
PUSH R16
RCALL WREEP ;写入一个字节
INC YL
BRNE WRETI
INC YH ;EEPROM末地址为$1FF
WRETI: POP R16
OUT SREG,R6
RETI
WREEP: OUT EEARH,YH ;
OUT EEARL,YL ;写入地址送入EEAR
LD R16,X+ ;取数据,调指针
OUT EEDR,R16 ;数据写入EEPROM数据寄存器
SBI EECR,2 ;设置EEPROM写入总使能位EEMWE
SBI EECR,1 ;设置EEPROM写入使能位EEWE
RET
;范例94 ;精确定时产生0.1秒信号
;用定时/计数器1定时,不分頻定出0.1秒信号,由PC5脚输出正脉冲。
;晶体4.000119MHZ,计400012个数定出0.1秒信号
;对定时/计数器1重装常数进行加法补偿(扣除自然计数和补偿占用时间).
;加法补偿若产生进位,将中断次数减1
.ORG $000 ;精确定时产生0.1秒信号
STRT24: RJMP RST24
.ORG $006 ;8515 t1 overflow vector
RJMP T1_OVFL ;400012=65536*7-58740=7*$10000-$E574/故TCC=$E574
.ORG $00D
RST24: LDI R16,HIGH(ramend)
OUT SPH,R16
LDI R16,LOW(ramend)
OUT SPL,R16
SBI DDRC,5 ;PC5,0.1秒号输出(高有效)
CBI PORTC,5
LDI R16,1 ;不分頻
OUT TCCR1B,R16
LDI R16,$E5
OUT TCNT1H,R16
LDI R16,$74
OUT TCNT1L,R16 ;写入时间常数TCC
LDI R16,$80
OUT TIMSK,R16 ;允许定时/计数器1溢出中断
LDI R16,7 ;7次中断输出0.1秒号
MOV R6,R16
SEI ;中断总使能
HH1A: RJMP HH1A ;
T1_OVFL:PUSH R16
PUSH R17
IN R7,SREG
DEC R6 ;中断次数减一
BRNE GOON10 ;0.1秒时间到?
LDI R16,7
MOV R6,R16 ;重新装入中断次数
SBI PORTC,5 ;0.1秒号输出前沿
IN R17,TCNT1L ;*
IN R16,TCNT1H ;*读入TCNT1自然计数值
LDI R18,$7C ;*TCC=$E574
ADD R17,R18 ;*TCC+8=$E57C
LDI R18,$E5 ;*8条单周期补偿指令占用8个时钟周期
ADC R16,R18 ;*修正后TCC=$E574+(TCNT1)+8
OUT TCNT1H,R16 ;*
OUT TCNT1L,R17 ;*重新装入补偿修正后的TCC
BRCC GOON09
DEC R6 ;加法补偿若产生进位,将中断次数减1
GOON09: ;. ;数据处理略
;.
;.
;.
;.
RCALL ACLK1 ;0.1秒走时软时钟
RJMP GOON11
GOON10: CBI PORTC,5 ;输出信号后沿
GOON11: POP R17
POP R16
OUT SREG,R7
RETI
;范例95
;用定时/计数器1定时,不分頻定出1秒信号,由PC5脚输出正脉冲
;晶体4.000133MHZ,计4000133个数定出1秒信号
;对定时/计数器1重装常数进行加法补偿(扣除自然计数和补偿占用时间).
;加法补偿若产生进位,将中断次数减1
.ORG $000 ;精确定时产生秒号
STRT25: RJMP RST25
.ORG $006
RJMP T1_OVFB ;4000133=62*65536-63099=62*$10000-$F67B/故TCC=$F67B
.ORG $00D
RST25: LDI R16,HIGH(ramend)
OUT SPH,R16
LDI R16,LOW(ramend)
OUT SPL,R16
SBI DDRC,5 ;PC5输出秒信号(正脉冲)
CBI PORTC,5
LDI R16,1 ;不分頻
OUT TCCR1B,R16
LDI R16,$F6 ;
OUT TCNT1H,R16 ;写入TCC高8位
LDI R16,$7B ;
OUT TCNT1L,R16 ;写入TCC低8位
LDI R16,$80 ;
OUT TIMSK,R16 ;允许T/C1溢出中断
LDI R16,62 ;62次中断定出秒号
MOV R6,R16
SEI ;
HH1B: RJMP HH1B ;等待中断
T1_OVFB:PUSH R16
PUSH R17
IN R7,SREG
DEC R6 ;到62次中断?
BRNE GOON12
LDI R17,62
MOV R6,R17 ;重装中断次数
SBI PORTC,5 ;输出秒信号
IN R17,TCNT1L ;*
IN R16,TCNT1H ;*读入T/C1自然计数值
LDI R18,$83 ;*TCC=$F67B
ADD R17,R18 ;*TCC+8=$F683
LDI R18,$F6 ;*8条单周期补偿指令占用8个时钟周期
ADC R16,R18 ;*
OUT TCNT1H,R16 ;*
OUT TCNT1L,R17 ;*重新装入补偿修正后的TCC
BRCC GOON19
DEC R6 ;加法补偿若产生进位,将中断次数减1
GOON19: ;. ;数据处理略
;.
;.
;.
;.
RJMP GOON13
GOON12: CBI PORTC,5 ;秒号后沿
GOON13: POP R17
POP R16
OUT SREG,R7
RETI
;范例96
;AVR频率计程序
;运作特点如下:
;此程序为一完整频率测量显示程序,所测频率较高(2MHZ),使用4兆晶振
;程序兼有启动看门狗及对其管理功能
;以TCNT0精确定时输出秒号作为捕获信号,用TCNT1对被测信号频率计数
;用TCNT0直接对(8515)4兆晶振计数产生秒号,定时精度达1Hz 主常数选为256(即0)
;由PA0输出精确定时产生的秒信号(与ICP脚相连)捕获TCNT1计数值,相减计算频率
;将频率转换为十进制数,装入显示缓存区,调DSPA子程序显示之(参考范例27和图4-5)
;重装TCC时对TCC进行修正,若修正(减法)计算不产生借位,将中断次数n减1
;被测频率可近2兆,故须设1字节扩展计数器,以tcnt1溢出中断对其计数(共3字节计数器)
;在TCNT1捕获中断服务中,以3字节减法计算频率,并置位T标志;若TCNT1溢出标志置位
;必须提前增1扩展计数器,并将TCNT1溢出标志清除(不再增1扩展计数器),再计算频率.
;TCNT1溢出中断优先级高于TCNT0,故TCNT1中断服务可能影响秒号精度,导致测量误差
;可以排队法剔除坏值,即将几个连续采样按大小顺序排队,‘掐头去尾'只留中间再作平均.
;也可以监视LED显示,连续3秒稳定显示(高频测量允许有2Hz误差)即为所测频率正确值.
;若晶振采用12兆,被测信号频率(暂空比1:1或接近1:1)可接近6兆.
.ORG $000
STRT26: RJMP RST26 ;实测8515晶振频率4.000167MHZ 计4000167个数为1秒
.ORG $003
RJMP T1_CAPT ;T/C1捕获中断
.ORG $006
RJMP T1_OVRF ;T/C1溢出中断
.ORG $007
RJMP T0_OVFB ;T/C0溢出中断
.ORG $00D ;4000167=256*15626-89=256*$3D0A-89/故TCC=89 n=15626
RST26: LDI R16,HIGH(ramend)
OUT SPH,R16
LDI R16,LOW(ramend)
OUT SPL,R16
SBI DDRA,0 ;PA0输出秒定时信号,捕获频率计数值
CBI PORTA,0 ;初始为低
CLR R22
CLR R21
CLR R20 ;R20,R21,R22为频率量瞬时计数采样
CLR R2
WDR
LDI R16,$0D ;启动看门狗,溢出时间为0.49"
OUT WDTCR,R16 ;写入看门狗控制寄存器
CLR XH
LDI XL,$6C ;set the display buffer pointer
T26LP: ST X+,R2
CPI R26,$74
BRNE T26LP ;清除$6C--$73
LDI R16,$01 ;T/C0为定时器,不分频
OUT TCCR0,R16
LDI R16,89 ;
OUT TCNT0,R16 ;写TCC到TCNT0
LDI R16,$C6 ;上升沿捕获,允许噪音滤除,外部脉冲计数
OUT TCCR1B,R16
LDI R16,$8A ;允许T/C1捕获,溢出以及T/C0溢出中断
OUT TIMSK,R16 ;
LDI R16,$3E ;设15626(=$3D0A)次中断(高位字节已增1)
MOV R1,R16 ;
MOV R19,$0A ;
SEI ;
HH1C: BRTS HH2C ;已采集到频率?
RCALL DSPA ;仍显示原数据
RJMP HH1C
HH2C: CLT ;频率量已在R3,R4,R5
MOV R9,R3
MOV R10,R4
MOV R11,R5
RCALL CONV1 ;翻为十进制数(R12R13R14R15<--R9R10R11)
LDI XL,$74
CLR XH
LDI YL,15
CLR YH
HHLOP: LD R16,Y ;分解十进制数,送入LED显示区($6C--$73)
ADNI R16,$0F
ST -X,R16
LD R16,Y
SWAP R16
ANDI R16,$0F
ST -X,R16
DEC YL
CPI R26,$6C ;分解完毕?
BRNE HHLOP
RJMP HH1C ;显示新数据
T0_OVFB:SEI ;TCNT0溢出,允许中断嵌套
PUSH R16
IN R8,SREG
DEC R19
BRNE GOON13
CBI PORTA,0 ;秒信号后沿
DEC R1 ;到15626次中断?
BRNE GOON13
SBI PORTA,0 ;秒定时捕获信号前沿
IN R16,TCNT0 ;*读TCNT0自然计数值
SUBI R16,164 ;*89之补为167,考虑补偿操作本身耗时,减去164
OUT TCNT0,R16 ;*第15626次中断后,重新装入TCC=89+(TCNT0)+3到TCNT0
LDI R16,$3E
MOV R1,R16 ;重新装入中断次数
LDI R19,$0A
BRCC GOON13 ;补偿操作如有借位,将中断次数减1
DEC R19 ;->252 253 254 255 | 0 1 2 3 4 5...加法计数方向-->
GOON13: POP R16 ; | | | | | | | | | | |
OUT SREG,R8 ;<--15626次范围-->|<-15625次范围(补偿后进(借)位
;C=l)->
RETI
T1_OVRF:IN R18,SREG ;TCNT1溢出中断服务
INC R3 ;R3为TCNT1扩展字节
OUT SREG,R18
RETI
T1_CAPT:IN R6,SREG ;T/C1捕获中断
PUSH R16
IN R5,ICR1L
IN R4,ICR1H
MOV R16,R22
MOV R22,R5
SUB R5,R16
MOV R16,R21
MOV R21,R4 ;与上一次采集的频率量相减,得到频率值
SBC R4,R16
IN R16,TIFR
SBRS R16,7
RJMP T1CP1
INC R3 ;8515TCNT1溢出中断,预先对扩展字节计数
LDI R16,$80 ;并将溢出标志清除,(中断返回后不再计数)
OUT TIFR,R16 ;清除TIFR,7
T1CP1: MOV R16,R20
MOV R20,R3
SBC R3,R16 ;采集频率量在R3,R4,R5
SET ;建采集频率量标志
POP R16
OUT SREG,R6
RETI
;范例97 时基资源共享式综合测量系统
; 本时基资源共享式综合测量系统,具有精确定时PWM输出、输入捕获测外部信号
;周期、获取TCNT1溢出中断信号等多种功能。特点是TCNT1启动之后即不停运行。
; 时基资源共享式PWM的特点在于装入比较匹配寄存器之数据方式,它不是在比较
;匹配达到时清除定时/计数器,再装入高(或低)电平时间常数:而是当比较匹配
;达到时以定时/计数器当前值加上时间常数后将和装入比较匹配寄存器,二者效果
;是相同的。可称前者为静态设置,后者为动态设置。后者因不停运行定时/计数器
;,其资源可同时用于输出比较匹配A及B、输入捕获、定时信号输出等等。
; 本程序使用晶体标称值4MHZ实测为4,000,236HZ。使用定时/计数器1直接
;对主频精确定时设定PWM高低电平的维持时间。以ICP脚输入被测周期脉冲信号。
; 本程序PWM之暂空比与范例51相同,为5毫秒(高):10毫秒。故维持
;高电平的时间常数为4,000,236÷200=20,001,维持低电平的时间常数为
;4,000,236÷100=40,002。此即输出比较匹配A达到时交替写入比较匹配寄
;存器OCR1A之对TCNT1当前内容的超前值。
; 因以TCNT1直接对主频计数,频率高周期短,输入捕获的外部信号周期不能
;大于65536÷4,000,236=0.01638(秒)即16.38毫秒(但也不能太小,对频率
;较高的脉冲信号应改为测频率)。以相邻两次捕获值相减之差除以主频得到被测信
;号之周期(单位为秒)。
; 为避免小数除法运算,可将相邻两次捕获值相减之差先乘以1,000,再将乘积
;除以主频,将得到以毫秒为单位的周期值;考虑到除法子程序DIV16只实现整数
;除法,且除数不能大于65535,可将主频缩小100倍,即以40,002作除数,故
;除得之商扩大了100倍。这样将整数商二翻十后,其末两位皆为小数。本程序采用
;这种计算方法。并在主循环程序中调DSPA子程序显示所测周期值。。
; 若将以上算法中乘以1,000改为乘以10,000,并增加对商的万位转换,
;其余保留不变,则所得商数末3位皆为小数位。本算法精度高于上一种方法,如有
;提高测量精度之必要,应采用后种算法。
; 若扩大测量信号周期,应对TCNT1溢出信号计数,做3字节减法(见范例96)
;后再计算被测信号周期(除以4,000,236)。所测信号周期可达4.194秒。
; 本示例定时精度可与范例51做如下比较:本例中高低电平分别对主频计数
;20,001个和40,002个。范例51中高低电平分别对主频计数19,968个和
;40,000个。本示例定时精度明显高于范例51。
; 本示例TCNT1产生溢出中断之周期为16.38毫秒,其频率约为61HZ。在TCNT1
;溢出中断服务子程序中由PA3以正脉冲形式输出该信号。
.ORG $000 ;USE 8535
STRT43: RJMP RST43 ;5.0000MS(高):9.9999MS(低) 晶振4,OOO,236HZ
.ORG $005
RJMP T1_CP43 ;T/C1输入捕获中断
.ORG $006
RJMP T1_CA43 ;T/C1输出比较匹配A中断
.ORG $008
RJMP T1_OV43 ;TCNT1溢出中断
.ORG $011
RST43: LDI R16,HIGH(RAMEND)
OUT SPH,R16
LDI R16,LOW(RAMEND)
OUT SPL,R16
LDI R16,$80 ;T/C1比较匹配A达到时,清除输出脚OC1A
OUT TCCR1A,R16
LDI R16,$41 ;不分频,比较匹配达到不清TCNT1;上升沿捕获/禁止噪音滤除
OUT TCCR1B,R16
SBI DDRD,5
SBI PORTD,5 ;PD5(OC1A)初始化输出为高
SBI DDRA,3 ;PA3为TCNT1溢出中断信号输出
CBI PORTA,3 ;PA3输出为低
LDI R16,$4E
OUT OCR1AH,R16
LDI R16,$21 ;写比较匹配寄存器($4E21=20001脉宽5毫秒)
OUT OCR1AL,R16
LDI R16,$34 ;允许输入捕获/输出比较匹配A/TCNT1溢出中断
OUT TIMSK,R16
CLR R21
CLR R20 ;捕获值暂存单元
CLR XH
LDI XL,$6C
CLR43: ST X+,R20
CPI XL,$74
BRNE CLR43 ;清除显示区$6C--$73
SEI
HH43: RCALL DSPA ;背景程序:显示捕获频率信号之周期,单位:毫秒
BRTC HH43
RCALL FIL2 ;T=1,已捕获到数据在R4,R5/先关显示
CLT
MOV R14,R4
MOV R15,R5
LDI R16,3
MOV R12,R16
LDI R16,$E8 ;取立即数1000(=$3E8)
MOV R13,R16
RCALL MUL16 ;乘以1000
LDI R16,$9C ;使周期单位为毫秒
MOV R10,R16
LDI R16,$42 ;$9C42=40002
MOV R11,R16
RCALL DIV16 ;除以立即数40002,得到被测脉冲周期之单位为毫秒,且含因子100
MOV R16,R14
MOV R17,R15
LDI R18,3
LDI R19,$E8
RCALL CONVT ;二翻十,得千位
STS $70,R11 ;送入显示区
CLR R18
LDI R19,$64
RCALL CONVT ;二翻十,得百位
LDI R19,-$29 ;在百位处加小数点(百位实为个位)
SUB R11,R19
STS $71,R11 ;送入显示区
LDI R19,10
RCALL CONVT ;二翻十,得十位
STS $72,R11
STS $73,R17 ;小数送入显示区
RJMP HH43 ;转去显示新采样数据
CONVT: CLR R11
COVLOP:SUB R17,R19
SBC R16,R18 ;减去十进制数某位之权
BRCS CONVCM
INC R11 ;够减,增权
RJMP COVLOP
CONVCM:ADD R17,R19 ;否则恢复余数
ADC R16,R18
RET
T1_CA43:SEI
IN R1,SREG
IN R24,TCCR1A
SBRS R24,6
RJMP OUTLW ;当前输出低电平,转
IN R24,OCR1AL
IN R25,OCR1AH
SUBI R24,$DF ;LOW(-20001)
SBCI R25,$B1 ;HIGH(-20001)/$B1DF为20,001之补码
OUT OCR1AH,R25
OUT OCR1AL,R24 ;写入高电平维持时间超前值
LDI R24,$80 ;比较匹配A达到时,OC1A输出为低
OUT TCCR1A,R24
OUT SREG,R1
RETI
OUTLW: IN R24,OCR1AL
IN R25,OCR1AH
SUBI R24,$BE ;LOW(-40002)
SBCI R25,$63 ;HIGH(-40002)/$63BE为40,002之补码
OUT OCR1AH,R25 ;
OUT OCR1AL,R24 ;写入低电平维持时间超前值
LDI R24,$C0 ;比较匹配A达到时,OC1A输出为高
OUT TCCR1A,R24
OUT SREG,R1
RETI
T1_CP43:IN R3,SREG ;T/C1捕获中断
IN R5,ICR1L
IN R4,ICR1H
MOV R17,R21
MOV R21,R5
SUB R5,R17
MOV R17,R20
MOV R20,R4 ;与上一次采集的频率量相减,得到频率值
SBC R4,R17 ;在R4,R5中
SET ;建采集频率量标
OUT SREG,R3
RETI
T1_OV43:SEI
SBI PORTA,3 ;OUTPUT THE 61HZ PULS
SBI PORTA,3
SBI PORTA,3
SBI PORTA,3
CBI PORTA,3 ;脉冲宽度2微秒
RETI
;范例98
;智能型RS-232与RS-485标准转换程序
;MAX232'R1OUT接MAX483'DI/MAX483'RO接MAX232'T1IN
;由TCNT0配合PB0以软件接收RS-232数据 对485进行监控:PB1接DE和/RE
;AVR对485发来数据不接收,该数据经MAX483'RO->MAX232'T1IN-->RS-232远端
;数据起始位下降沿引起中断接收,中断服务一开始,将对RS-485的控制改为允发禁收使RS-
;232发来数据直接通过RS-485向远端发送
;当收到RS-232数据结束符$03后,经半位延时,对RS-485的控制改为允收禁发 使能接收RS-
;485远端发来数据(故要求经RS-232发来数据要以$03为结束符,对来自RS-485数据无此要求)
;可采用avr专门管理两种标准转换方案(可采用少脚ATtiny系列),也可采取主avr兼管方案.
;主avr兼管时,它既接收处理完整串行数据块(及执行其它程序),又控制通讯标准转换.
.EQU DATA4=$100
.ORG 0 ;R16:THE BIT SEQUENCE COUNTER R17:WORKING
;REG.R18:FLAG UNIT
STRT3S: RJMP RST3S ;BAUD RATE:9600 USE 8515/may REPLACE BY ATtiny serials
.ORG $007 ;$007(8515)
RJMP T0_OF
.ORG $00D
RST3S: LDI R17,HIGH(ramend)
OUT SPH,R17
LDI R17,LOW(ramend)
OUT SPL,R17
LDI R17,$02 ;8535:$01
OUT TIMSK,R17 ;timsk,1(允许tcnt0中断)
LDI R17,6 ;设外部脉冲计数
OUT TCCR0,R17
CBI DDRB,0 ;T0 为输入
LDI R17,$FF
OUT TCNT0,R17 ;计1个数即中断
SBI DDRB,1 ;PB1输出,控制DE和/RE
CBI PORTB,1 ;禁止485发送
SEI
CLR R18
CLR R16
HERE0: SBRC R18,0
BRNE RST3S ;无错误标志循环
SBRS R18,1
BRNE HERE0 ;未收到数据块结束符($03)循环
LDI R16,64
HERE1: DEC R16
BRNE HERE1 ;延时(48+3.5=)52微秒(超过半位,以等待半个停止位发过去)
RJMP RST3S ;以使远端485正确收到停止位
T0_OF: SBI PORTB,1 ;允许485发送
IN R11,SREG
PUSH R17
CPI R16,0 ;接收起始位?
BRNE T0SV11
LDI R17,2 ;YES
OUT TCCR0,R17 ;改为内定时,8分频(4MHZ/8)
LDI R17,232 ;半位定时常数24,定出48微秒(<52微秒)
OUT TCNT0,R17
RJMP T0SV7
T0SV11: CPI R16,1 ;半位定时后,查起始位有效性
BRNE T0SV12
SBI PORTB,0
SBIC PINB,0 ;低电平为有效
RJMP T0ER ;否则转错误处理
RJMP T0SV62 ;
T0SV12: CPI R16,10 ;停止位?
BRNE T0SV3S
CLR R16 ;是
SBI PORTB,0 ;停止位为l?
SBIS PINB,0
RJMP T0ER ;否,转错误处理
MOV R17,R15
CPI R17,3 ;收到结束符$03?
BRNE T0SV13
ORI R18,2 ;结束符收到
OUT TCCR0,R16 ;停止TCNT0
RJMP T0SV63
T0SV13: LDI R17,6
OUT TCCR0,R17 ;改为外定时
LDI R17,$FF ;停止位下降沿即中断
OUT TCNT0,R17
RJMP T0SV63
T0SV3S: BRCC T0ER ;出错(位计数器超过10)
CLC ;2--9:接收一位数据
SBI PORTB,0 ;本位为1?
SBIC PINB,0
SEC
ROR R15 ;接收数据组织到R15
T0SV62: IN R17,TCNT0 ;读入TCNT0自然计数值
INC R17
SUBI R17,52 ;1位时间常数为52
OUT TCNT0,R17 ;补偿后回送定时常数
T0SV7: INC R16 ;位计数器增1
T0SV63: POP R17
OUT SREG,R11
RETI
T0ER: SBR R18,1 ;出错标志 ERR. FLAG
CLR R16
OUT TCCR0,R16 ;停止TCNT0
RJMP T0SV63
;范例99
; 串行日历/时钟芯片DS1302的应用子程序。AVR与DS1302接口为:PC0--SCLK,PC1--DATA,PC2--/RST。请参看本范例之附图。
; 结构与运作特点如下:
;(1)采用标准频率晶体(32768HZ),便于调整(可加电容补偿),可对PC0/PC1/PC2加提拉电阻。
;(2)DS1302只有8只脚,小巧精悍,耗电省,抗干扰.便于与单片机接口,以串行方式按位读写数据.
;(3)以备用电池供电保存数据,断电后自动执行写保护,故可靠性高。上电后须用指令解除写保护。
;(4)片内除8个时钟日历单元外还有31个RAM单元,可作为系统断电保护数据存储单元
;(5)可以并发(BURST,即连续)方式读写8个时钟日历单元(秒/分/时/日/月/周/年/年)或读写31个RAM单元,
; 命令如下:
; $BE为以并发方式写8个时钟日历单元,$BF为以并发方式读8个时钟日历单元。
; $FE为以并发方式写31个RAM单元,$FF为以并发方式读31个RAM单元。
;(6)除以并发方式读写外,还可按字节读写,但读写前须先写入命令。
; 读写命令格式为:1 Y A4 A3 A2 A1 A0 X,最高位为1表示命令有效,Y=0,选择读写时间/日期
; Y=1,选择读写片内RAM,A4-A0,片内RAM/时钟单元地址,X=0,选择写操作,X=1,选择读
;操作.
;(7)串行时钟上升沿写入一位数据,下降沿读出一位数据;且读写只有在/RST信号为高时才有效。
; 故要求/RST信号有效前时钟信号应已就绪。
;(8)本程序AVR时钟为4MHZ,若使用其他时钟,重新调整读写延时时间(程序中NOP之个数)
;(9)对RAM并发读写方法可参考并发读写时钟日历子程序进行.
;1)并发(BURST)方式写时钟日历单元(时钟日历数据 秒,分,时,日,月,周,年,年分别在R8--R15,)
WBURST: CLR YH ;
LDI YL,8 ;数据指针,首指秒单元R8
RCALL DEPRV ;解除写保护(写入$8E00)
CBI PORTC,0 ;为上升沿写作准备(SCLK升高)
NOP
NOP
NOP
SBI PORTC,2 ;复位信号变高(SETB RST)
NOP
NOP
NOP
NOP
NOP
NOP
LDI R18,$BE ;BURST(wr.) ADDR.&INSTRUC.(命令$BE)
RCALL WBYTE ;
WLOP: LD R18,Y+
RCALL WBYTE ;写入1字节数据
CPI YL,16
BRNE WLOP ;数据都写完?
CBI PORTC,2 ;禁止读写
NOP
NOP
NOP
NOP
CBI PORTC,0
RET
;2)并发(BURST)方式读时钟日历单元(时钟日历数据 秒,分,时,日,月,周,年,
;分别读到R8--R14中)
RBURST: CLR YH ;首指R8
LDI YL,8
SBI DDRC,0 ;SCLK 输出
SBI DDRC,2 ;WR/RD ENABLE 输出
SBI PORTC,0 ;时钟SCLK初始输出为低
NOP
NOP
NOP
SBI PORTC,2 ;复位有效,允许时钟相关沿有效
NOP
NOP
NOP
NOP
NOP
NOP
LDI R18,$BF ;BURST(rd.) ADDR.&INSTRUC.
RCALL WBYTE ;写入并发读命令
RLOP: RCALL RBYTE ;读出一字节时钟/日历数据
ST Y+,R18 ;存储
CPI R28,15
BRNE RLOP ;数据都读完?
CBI PORTC,2 ;禁止读写
NOP
NOP
NOP
NOP
CBI PORTC,0 ;使SCLK变低
RET
;3)解除写保护子程序(对DS1302写入$8E,$00)
DEPRV: SBI DDRC,0 ;SCLK 输出
SBI DDRC,2 ;WR/RD ENABLE 输出
CBI PORTC,0 ;时钟SCLK初始输出为低
NOP
NOP
NOP
SBI PORTC,2 ;复位有效,允许时钟相关沿有效
NOP
NOP
NOP
NOP
NOP
NOP
LDI R18,$8E
RCALL WBYTE
NOP
NOP
CLR R18
RCALL WBYTE ;写入$8E和$00
CBI PORTC,2 ;禁止读写
NOP
NOP
NOP
NOP
CBI PORTC,0
RET
;4)对DS1302秒,分,时单元写入3字节数据
WTIME: CLR YH ;
LDI R17,$80 ;写秒单元命令
LDI YL,8 ;R8(秒)R9(分)R10(时)
LDI R19,3
WCOM: RCALL DEPRV ;解除写保护
WLOP1: CBI PORTC,0 ;时钟SCLK初始输出为低
NOP
NOP
NOP
SBI PORTC,2 ;复位有效,允许时钟相关沿有效
NOP
NOP
NOP
NOP
NOP
NOP
MOV R18,R17
RCALL WBYTE ;写入一字节命令
SUBI R17,$FE ;指向时间下一单元
LD R18,Y+
RCALL WBYTE ;写入时间单元1字节
CBI PORTC,2 ;禁止读写
NOP
NOP
NOP
NOP
CBI PORTC,0
DEC R19
BRNE WLOP1 ;写完规定字节?
RET
;5)写入日期子程序
WDATE: CLR YH ;
LDI YL,11 ;R11(日)R12(月)R13(周)R14(年)
LDI R17,$86 ;写日单元命令
LDI R19,4
RJMP WCOM
;6)读出时间子程序
RTIME: CLR YH ;
LDI R17,$81 ;读秒单元命令
LDI YL,8 ;读出数据送到R8(秒)R9(分)R10(时)
LDI R19,3
RCOM: SBI DDRC,0 ;SCLK 输出
SBI DDRC,2 ;WR/RD ENABLE 输出
RLOP1: CBI PORTC,0 ;时钟SCLK初始输出为低
NOP
NOP
NOP
SBI PORTC,2 ;允许读写
NOP
NOP
NOP
NOP
NOP
NOP
MOV R18,R17
RCALL WBYTE ;写入读命令
SUBI R17,$FE ;指向下一单元地址
RCALL RBYTE ;读出一字节数据
ST Y+,R18
CBI PORTC,2 ;禁止读写
NOP
NOP
NOP
NOP
CBI PORTC,0 ;时钟变低
DEC R19
BRNE RLOP1 ;已读出规定字节?
RET
;7)读出日期子程序
RDATE: CLR YH
LDI YL,11 ;读出数据放入R11(日)R12(月)R13(周)R14(年)
LDI R17,$87 ;读出日单元命令
LDI R19,4
RJMP RCOM
;8)将R18中数据写入DS1302
WBYTE: LDI R16,8 ;8位/字节
SBI DDRC,1 ;PC1为输出
WB1: CBI PORTC,0 ;时钟SCLK初始输出为低
ROR R18 ;一位数据传到进位C
BRCC WB10
SBI PORTC,1
RJMP WB2
WB10: CBI PORTC,1 ;1位数据输出到数据线(DS1302'DATA BUS)
WB2: NOP
NOP
NOP
NOP
SBI PORTC,0 ;上升沿写入一位
DEC R16
BRNE WB1 ;8位数据都写完?
RET
;9)读出DS1302一字节数据在r18中
RBYTE: LDI R16,8 ;8位/字节
CBI DDRC,1 ;PC1输入
RD1: CBI PORTC,0 ;下降沿读出一位数据
NOP
NOP
NOP
NOP
SBI PORTC,1 ;上拉电阻激活
CLC
SBIC PINC,1
SEC ;读出一位数据并-->C
ROR R18 ;组织数据
SBI PORTC,0 ;SCLK升高,为下位读准备
DEC R16
BRNE RD1 ;8位数据都写完?
RET
;范例100 ;DALLAS 18B20测温程序
; DS18B20为美国DALLAS公司(已被MAXIM公司并购)生产的单线数字温度传感器,
;可将温度信号直接转换成数字信号供单片机处理,所测温度范围-55°C~125°C,精度
;达0.5°C,转换时间为750毫秒。该器件出厂时带有固化的8字节‘身份’编号,最低
;位字节为家族号码$28,接下来6字节为器件流水线编号,最高位字节为CRC校验码。
;有读ROM,匹配ROM,启动温度变换,读RAM数据等十余种命令对18B20操作。使用
;18B20之前要用读ROM命令读出其身份编号并记录。一条单总线上可挂接任意多个
;18B20,单片机通过单总线发出启动转换命令之后,所有18B20同时进行温度转换。
;经等待延时后,单片机通过发出匹配ROM命令,18B20编号,读RAM数据等命令等,
;读取各18B20温度数据组(每组数据共9字节)。程序中对每组数据都进行CRC校验。
;温度数据占2字节,为补码形式。最高位为符号位,0为正1为负。高位字节和低位
;字节的高4位为温度整数部分,最低4位为温度小数部分。程序中对温度数据进行取
;补、左移将整数部和小数部分分离,再将它们分别转成十进制数。整数二翻十用减
;十进一法,小数二翻十采用按权累加法(并以减负替代加正),再将它们冠以数符
;并加小数点送入DSPA子程序的显示缓存区,调该子程序进行显示。
; 由于DSPA子程序中含0.462秒定时复位看门狗指令,故以调用DSPA为主循环程序
;不必考虑对看门狗管理问题(初始化设置看门狗溢出周期为0.49秒)。
; DS18B20不象一般串行器件既有数据线又有时钟线,它只有一条数据线,故它
;只能靠较严格的时序脉冲信号进行读写,程序中多种延时环节就是为调整时序所
;设。本程序AVR使用4MHZ时钟,如改变时钟,应按定时时间重新确定延时常数。
; 18B20的使用可采用窃电方式,此种方式要将18B20电源端接地。在线缆长测点
;的应用场合,可控制MOS管取得数据线的强上拉,以提高总线驱动能力。
;对18B20的ROM操作命令如下:
; 命令 代码
; 读ROM $33
; 匹配ROM $55
; 跳过ROM $CC
; 搜索ROM $F0
; 告警搜索 $EC
; 对18B20的存储器操作命令如下:
; 命令 代码
; 写暂时存储器 $4E
; 读暂时存储器 $BE
; 复制暂时存储器 $48
; 启动温度变换 $44
; EEPROM内容调出 $B8
; 读电源 $B4
; 有关18B20初始化,读写命令,读写时序等请参看参考文献9和10,18B20与AVR接口
;见程序附图,CRC检测请参看4.8.4小节。
START2:LDI R16,2
OUT SPH,R16
LDI R16,$5FH
OUT SPL,R16 ;堆栈指针初始化
SBI DDRA,2
CBI PORTA,2 ;MOS管不上拉
RCALL RESET ;复位18B20
WDR
LDI R16,$0D
OUT WDTCR,R16 ;启动看门狗,溢出时间为0.49秒
CLR R2 ;执行请除看门狗指令WDR的定时器初始化请除
LDI R16,$CC ;跳越ROM(SKIP ROM)
RCALL WB
LDI R16,$44 ;START DS18B20 TEMPORATURE CONVERTING
RCALL WB
CLR XH
CLR YH ;指针高位字节清除
LDI YL,$6C
CLR44:ST Y+,YH
CPI YL,$74
BRNE CLR44 ;清除显示缓存区($6C~$74)
LDI R17,163 ;4.618×163=753(ms)
STR0: RCALL DSPA
DEC R17
BRNE STR0 ;总共延时753ms,等待转换完成
RCALL RESET ;再次复位DS18B20
LDI XL,$60 ;温度数据指针
LDI R17,4 ;总共4只DS18B20
LDI ZH,HIGH(DATA*2)
LDI ZL,LOW(DATA*2);18B20身份数据指针
LOOP0:LDI R16,$55 ;匹配ROM命令 (match rom)
RCALL WB ;写入18B20
LDI R18,8 ;18B20身份数据共8个字节固化在FLASH中
LOOP4:LPM ;取数据
MOV R16,R0 ;转入R16
RCALL WB ;写入18B20 1字节
ADIW ZL,1 ;指向下一字节
DEC R18
BRNE LOOP4 ;共写入8个字节
LDI R16,$BE ;读18B20数据存储器命令
RCALL WB ;写入该命令
LDI YL,$74
LOP40:RCALL RB ;读出18B20数据共9个字节
ST Y+,R16 ;存入$74-$7C
CPI YL,$7D
BRNE LOP40
RCALL RESET ;再次复位18B20
LDI YL,$74
RCALL CRC9 ;对读得数据进行CRC校验
TST R15
BREQ LLL
ERROR:LDI R16,15 ;CRC余式不为零,温度数据错误
LDI YL,$6C
EER1: ST Y+,R16
CPI YL,$74
BRNE EER1
ERR1: RCALL DSPA ;显示$FFFFFFFF,等待按键
SBRC R16,7
RJMP ERR1
RJMP START2 ;有键按下,重新启动
LLL: LDS R16,$74
LDS R15,$75 ;取温度数据 R15为HIGH BYTE
ST X+,R16
ST X+,R15 ;温度转入SRAM
BST R15,7 ;数符存于T
BRTC PLUS ;正数转
COM R16
COM R15
LDI R18,255
SUBI R16,R18
SBCI R15,R18 ;负数求补
PLUS: LDI R18,4
LOOP5:ADD R16,R16
ADC R15,R15
DEC R18
BRNE LOOP5 ;整数部分在R15
MOV R19,R16 ;小数部分转入R19
CLR R9 ;百位BCD予清
LDI R16,100
CP R15,R16
BRCS LOP51
INC R9 ;百位BCD存在(EXISTED)!
SUB R15,R16 ;减去100
LOP51:LDI R16,10
CLR R10
LOP52:SUB R15,R16
BRCS LOP53
INC R10
RJMP LOP52 ;十位及个位BCD转换
LOP53:ADD R15,R16 ;十位在R10,个位在R15
CLR R16
SBRC R19,7
SUBI R16,-$50 ;小数最高位值为0.5
SBRC R19,6 ;其余折半递减
SUBI R16,-$25 ;0.25
SBRC R19,5
SUBI R16,-$13 ;0.125
SBRC R19,4
SUBI R16,-6 ;0.0625
CPI R16,$0A ;产生非法BCD?(只可能在低位产生)
BRHC LOP54
SUBI R16,$FA ;小数部分二翻十/调整(在R16中)
LOP54:MOV R11,R16
LDI YL,$6C
LDI R16,$24
ST Y+,R16
ST Y+,R16
ST Y,R16
RTC LOP55
LDI R16,$14 ;负温度,加负号!
ST Y,R16
LOP55:INC YL
ST Y+,R9 ;百位BCD装入 $6F 单元
ST Y+,R10 ;十位BCD直接装入$70单元
MOV R16,R15
SUBI R16,-$29 ;个位BCD加小数点后
ST Y+,R16 ;装入$71单元
MOV R16,R11
SWAP R16
ANDI R16,$0F
ST Y+,R16
MOV R16,R11
ANDI R16,$0F
ST Y+,R16 ;分解小数BCD/并装入$72及$73单元
CLR R8
NORML:RCALL DSPA ;显示温度数据2.4秒
RCALL DSPA ;4.62ms×2×256=2.4s
DEC R8
BRNE NORML
DEC R17
BREQ HALT ;采集完4点温度?
RJMP LOOP0 ;未完循环
HALT: LDI R16,$1D
OUT WDTCR,R16
LDI R16,$15
OUT WDTCR,R16 ;已采集到4点温度,关看门狗
RDSPA:RCALL DSPA
SBRC R16,7
RJMP RDSPA ;无键按下,显示最后采集的温度
RJMP START2 ;否则再次启动
DATA: .DB $28,$3A,$13,$08,$00,$00,$00,$E5 ;18B20身份数据
.DB $28,$4A,$4D,$08,$00,$00,$00,$14
.DB $28,$3A,$19,$08,$00,$00,$00,$5E
.DB $28,$32,$33,$08,$00,$00,$00,$66
RESET: SBI DDRA,3 ;PA3为输出
CBI PORTA,3 ;负脉冲前沿
LDI R19,4
RES1: RCALL DL170
DEC R19
BRNE RES1 ;延时682.75微秒 (最短可555微秒)
SBI PORTA,3 ;负脉冲结束
LDI R18,146
RES2: DEC R18
BRNE RES2 ;延时110微秒
RCALL DL170 ;总共280微秒
CBI DDRA3 ;转为输入(CHANGE TO INPUT)
SBI PORTA,3 ;上拉电阻激活(PULL UP MOS ACTIVED)
CLC
SBIC PINA,3 ;18B20存在标志存于 C
SEC
RCALL DL170 ;再次延时
RET
DL170: LDI R18 ,224 ;延时170.75微秒(含RCALL和RET时间)
LP170: DEC R18
BRNE LP170
RET
WB: LDI R19,8 ;写入1字节数据
MOV R15,R19
SBI DDRA,3
LOOP2: CBI PORTA,3 ;数据线输出为低
LDI R19,23
LOP21: DEC R19
BRNE LOP21 ;延时17微秒
ROR R16
BRCC LOP22 ;1位数据由进位C转入PC3
SBI PORTA,3
RJNP LOP23
LOP22: CBI PORTA,3
LOP23: LDI R19,88
LOP24: DEC R19
BRNE LOP24 ;延时66微秒
SBI PORTA,3
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
DEC R15
BRNE LOOP2
RET
RB: LDI R19,8 ;读出1字节数据
MOV R15,R19
LOOP3: SBI DDRA,3
SBI PORTA,3 ;数据线输出为高
LDI R19,5
LOP31: DEC R19
BRNE LOP31 ;延时3.5微秒
CBI PORTA,3 ;数据线输出为低
LDI R19,6
LOP3A:DEC R19
BRNE LOP3A ;延时4.5微秒
SBI PORTA,3 ;数据线输出为高
LDI R19,26
LOP32: DEC R19
BRNE LOP32 ;延时19微秒
CBI DDRA,3 ;转为输入
SBI PORTA,3 ;上拉MOS管激活
CLC
SBIC PINA,3
SEC ;读出1位数据到C
LDI R19,88
LOP33:DEC R19
BRNE LOP33 ;延时66微秒
ROR R16
DEC R15
BRNE LOOP3
RET
CRC9: LDI R18,9 ;9字节数据CRC检测程序
CRC90: CLR R15 ;异或工作单元
LDI R19,$8C
MOV R14,R19
LP6: LDI R19,8
LD R16,Y+
LP7: LSR R16
ROR R15
BRCC NXRL
EOR R15,R14
NXRL: DEC R19
BRNE LP7
DEC R18
BRNE LP6
RET
;范例101 产生循环冗余检测(CRC)校验码表格子程序
;本子程序为生成$00--$FF共256个数据之双字节CRC校验码表子程序,生成多项式为
;P(X)=X16+X15+X2+1=$18005。因每一字节都生成两字节的CRC校验码,故CRC校
;验码表格长度为512字节。程序中规定将其放在片内SRAM$100--$2FF之中。也可将该
;表存放地址作为子程序的入口条件,在主程序中规定存放地址。使用的单片机为MEGA8
;/16/128;若使用8515单片机,须使用外部扩展SRAM;本子程序产生的CRC校验码表,
;可直接烧录到FLASH,或另行作为文件保存。
;若采用4字节的CRC校验码,表格长度达1024字节,则必须使用MEGA103/128等高档AVR
;单片机,或外扩SRAM的8515;故若处理的位序列信息不是很长,或对CRC检测的实时性
;要求不是很强,不必采用查表处理方式。
CRCTABL:LDI XH,$01 ;CRC-CODE-TABLE -GENERATING SUBPROGRAM
CLR XL ;CRCTABLE FROM $100 TO $2FF
CLR R16 ;USE MEGA8/16/128
LDI R17,$05
LDI R18,$80 ;P(X)=$18005
CRCT0: LDI R19,8
CLR R14
CLR R15 ;add 2bytes $00 behind a Bi
CRCT1: LSL R14
ROL R15
ROL R16
BRCC CRCT2
EOR R14,R17
EOR R15,R18
CRCT2: DEC R19
BRNE CRCT1
ST X+,R14
ST X+,R15
INC R16
BRNE CRCT0
RET
;范例102 快速生成位序列校验码/或对接收位序列进行循环冗余检测子程序
;100字节位序列m0,m1,m2,m3,m4,...m98,m99在发送方以递推方式生成CRC校验码子程序
;或在接收方对该序列进行CRC检测之子程序
;在发送方,本程序为CRC校验码生成子程序。将此位序列除以生成多项式P(X)
;=X16+X15+X2+1,将生成的CRC校验码(即余式)装入位序列的最低两位字节
;(冲掉m0,m1),将最终处理的位序列发送出去。
;在接收方,本程序为CRC检测子程序。将接收到的位序列除以生成多项式P(X)
;=X16+X15+X2+1,若将原位序列最低两位字节m0,m1恢复(即除得的余式R15R14
;与原始位序列最高两位字节相等),则为正确接收。
;本程序中循环次数为98,比位序列字节数少2。因为位序列最低两个字节m0,m1直
;接作为(第一个)余式,不对它们查取CRC校验码。
;X为按字节寻址位序列指针
;寻址CRC校验码表格先按字计算地址指针,将其增倍后变为按字节寻址。
CRCOUT:LDI XH,$1 ;THE BIT SEQUENCE IS IN $100---$163
CLR XL ;TOTAL 100 BYTES
LDI R16,$62 ;THE DATA BLOCK LENGTH IS 98(=100-2)
LD R14,X+ ;m0
LD R15,X+ ;m1
CRCO1:LD R13,X+ ;fetch m2 at the first!
LDI ZH,HIGH(DATA5)
LDI ZL,LOW(DATA5)
ADD R30,R13
CLR R13
ADC R31,R13
LSL R30
ROL R31 ;point to the CRC CODE!
LPM
EOR R14,R0
ADIW R30,1
LPM
EOR R15,R0 ;CRC CODE IN R14&R15(HIGH)!
DEC R16
BRNE CRCO1
STS $101,R15
STS $100,R14 ;将生成的CRC校验码放在位序列的最低两位字节中
RET ;或将原始位序列的最低两位字节恢复
DATA5:.DB $00,$00,$80,$05 ;THE CRC CODE TABLE
.DB $80,$0F,$00,$0A ;与范例101中SRAM$100--$2FF单元内容完全相同!
.DB $80,$1B,$00,$1E
;.................... ;其余略
end
代码及注释
DATA SEGMENT
A DW 1223,83,456,355,89
DW 948,5,123,789,567
CNT EQU ($-A)/2
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:MOV AX,DATA
MOV DS,AX
MOV CX,CNT-1
LOOP1:MOV DX,CX
MOV SI,0 ;每次排序都是从头开始的
LOOP2:MOV AX,A[SI] ;比较当前值与其下一个数
CMP AX,A[SI+2]
JNA L1 ;小于等于,即这两个数的顺序是递增的则跳转,直接进行后续操作
XCHG AX,A[SI+2] ;否则交换两值
MOV A[SI],AX
L1:ADD SI,2 ;LOOP2内循环中每次SI向后一位
LOOP LOOP2
MOV CX,DX ;实现细节与题目四中相同
LOOP LOOP1
MOV AH,4CH
INT 21H
CODE ENDS
END START
题目描述
数据按字节存储,按降序排序,实现排序。
DATA SEGMENT
A DB 12,11,5,9,3
DB 18,4,1,7,2
CNT EQU $-A
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:MOV AX,DATA
MOV DS,AX
MOV CX,CNT-1 ;CX置为初始循环次数
MOV BX,0
LOOP1:MOV DX,CX
MOV SI,1 ;因为改为DB,SI初始为1
LOOP2:MOV AL,A[BX]
CMP AL,A[BX+SI] ;比较两值
JNB L1 ;大于等于则直接跳转,进行后续步骤
XCHG AL,A[BX+SI]
MOV A[BX],AL ;否则交换两值
L1:INC SI ;改为DB,SI每次增加1
LOOP LOOP2
ADD BX,1 ;改为DB,BX每次增加1
MOV CX,DX ;细节注意,内循环,即LOOP2的循环次数每次都会减小1,而CX在经过下一个语句LOOP LOOP1时已经回减小1,再存入DX,再下一次取出时又被减小1,以此来实现每次CX都逐渐减小
LOOP LOOP1
MOV AH,4CH
INT 21H
CODE ENDS
END START
题目描述
有2个数组:
ary1 db 12,-35,0,126,-90,-5,68,120,1,-19
ary2 db 24,25,0,-38,-89,99,68,100,2,-20
比较两个数组的对应位,将大的数放在ary1数组中,小的数放在ary2中(要求采用子程序格式)
DATA SEGMENT
ARY1 DB 12,-35,0,126,-90,-5,68,120,1,-19
ARY2 DB 24,25,0,-38,-89,99,68,100,2,-20
LEN EQU $-ARY2
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:MOV AX,DATA
MOV DS,AX
MOV SI,OFFSET ARY1 ;SI作为ARY1指针
MOV DI,OFFSET ARY2 ;DI作为ARY2指针
LOP:MOV AL,[SI]
CMP AL,[DI] ;比较ARY1与ARY2中对应位置的值
JG NEXT ;若ARY1中值大直接跳转,进入下一步,否则需要交换
CALL FUN ;调用子程序进行两数组中值交换
NEXT:INC SI
INC DI ;指针后移
CMP SI,LEN
JNZ LOP ;若SI没到最后则跳转
MOV AH,4CH
INT 21H
FUN:MOV BL,[DI] ;子程序,用于交换两数组中的值
MOV [DI],AL
MOV [SI],BL
RET
CODE ENDS
END START
题目描述
试编写一个汇编程序,能对键盘输入的小写字母用大写字母显示出来(要求采用子程序格式,即采用子程序完成将小写字母转化成大写字母)。
代码及注释
DATA SEGMENT
MESSAGE DB 'This is not a lower case letter.',13,10,'$' ;不是小写字母的提示信息
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:MOV AX,DATA
MOV DS,AX
MOV AH,01
INT 21H ;输入字符结束
MOV BL,97
CMP AL,BL ;比较输入字符与a的ASCII码
JB EXIT ;输入字符不是小写字母则跳转到退出位置
MOV BL,122
CMP AL,BL ;比较输入字符与z的ASCII码
JA EXIT ;输入字符不是小写字母则跳转到退出位置
MOV BL,AL
MOV BH,0
CALL NEXT ;已判断是小写字母则调用子程序
MOV AH,4CH
INT 21H
EXIT:MOV AH,9 ;不是小写字母则输出不是的提示
MOV DX,SEG MESSAGE
MOV DS,DX
MOV DX,OFFSET MESSAGE
INT 21H
MOV AH,4CH ;中断程序
INT 21H
NEXT:MOV AL,32 ;小写变大写子程序
SUB BL,AL ;大小写字母之间ASCII码相差32,输入字符ASCII-32得到大写字母
MOV DL,BL
MOV AH,02
INT 21H ;输出字符
RET
CODE ENDS
END START
题目描述
在字节数组中找出第一个负数,并将该负数存入RES单元中;假设该数组中包含20个带符号数,且至少有1个负数。
代码及注释
DATA SEGMENT
RES DB ?
BUF DB 2,3,9,-4,8,6,0,3,-3,-4,8,7,9,2,3,5,-9,4,5,8
COUNT EQU 20 ;题目要求20个带符号数,直接存诚20
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:MOV AX,DATA;
MOV DS,AX
MOV SI,OFFSET BUF; ;SI作为指针
XOR AX,AX
LOP:MOV AL,[SI]
MOV BL,0
CMP AL,BL ;比较当前数和0
JL STORE ;有符号数比较,若当前数为负数,跳转
INC SI
JMP LOP ;题目要求一定有至少一个负数,所以可以使用无条件跳转
STORE:MOV RES,AL ;将AL,即负数的当前数存入RES
MOV AH,4CH
INT 21H
CODE ENDS
END START
题目描述
在STR到STR+99单元中存放着一个字符串,试编写程序测试该字符串中是否有数字,若有将CL置1,否则CL置0。
代码及注释
DATA SEGMENT
STR DB 'abc2uuuk_o$' ;字符串存入STR中
LEN EQU $-STR
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:MOV AX,DATA
MOV DS,AX
XOR CX,CX ;清空CX
MOV CL,LEN ;将字符串长度存入CX
MOV BL,100
CMP CL,BL ;比较CL和100,因为题目要求STR到STR+99,若长度大于100,则需要将长度置为100
JB X ;长度大于100则跳转,直接进行后续操作,否则把100存入CL
XOR CX,CX
MOV CL,100 ;100存入CL代替LEN
X:MOV SI,-1 ;SI作为指针,存入-1
NUMBER:INC SI ;SI加一
MOV BL,30H
CMP STR[SI],BL ;看字符的ASCII码是否大于等于0的ASCII码
JAE NEXT ;大于则跳转
LOOP NUMBER ;不是则不是数字,检查下一个字符串
MOV CL,0 ;运行到最后都不是,则CL置0
JMP EXIT
NEXT:MOV BL,39H
CMP STR[SI],BL ;看字符的ASCII码是否小于等于9的ASCII码
JBE LAB1 ;是则说明就是字符就是数字,可以将CL置1跳出程序
LOOP NUMBER ;不是则不是数字,检查下一个字符串
LAB1:XOR CX,CX
MOV CL,1 ;是数字会跳转到此处,CL置1
EXIT:MOV AH,4CH
INT 21H
CODE ENDS
END START
程序设计
汇编语言源程序
汇编语言源程序的结构
表达式
伪指令
格式
操作数
重复操作符
段定义伪指令
设定段寄存器伪指令
汇编语言源程序
汇编语言源程序的结构
一个完整的源程序通常由若干逻辑段组成,包括数据段、附加段、堆栈段和代码段。它们分别映射到存储器中的物理段上。每个逻辑段以segment语句开始,以ends结束,整个源程序用end语句结尾。
代码段中存放源程序的所有指令码、数据、变量等则放在数据段和附加段中。程序中可以定义堆栈段,也可以直接利用系统中的堆栈段。具体一个源程序中要定义多少个段应根据实际需求来定。
dseg segment ;定义数据段
data1 dw 0f865h ;DW是定义2字节空间
data2 dw 360ch
dseg ends
eseg segment ;定义附加段
sum dw 2 dup(?) ;定义存放结果
eseg ends
cseg segment ;定义代码段
assume cs:cseg,ds:dseg,es:eseg
start:mov ax,dseg
mov ds,ax ;初始化DS
mov ax,eseg
mov es,ax ;初始化ES
lea si,sum ;存放结果的偏移地址送SI
mov ax,data1 ;取被加数
add ax,data2 ;两数相加
mov es:[si],ax ;和送附加段的sum
hlt
cseg ends
end start
表达式
表达式不是一个指令,本身不能执行,在程序汇编时,汇编程序将表达式进行相应的运算,得出一个确定的值。
+-*/:当运符作为一个数值表达式时,其汇编结果就是一个数值。
逻辑运算符:只用于数值表达式,用于对数值进行位运算并得到一个结果。
关系运算符:有六个,EQ(相等),NE(不相等),LT(小于),GT(大于),LE(小于等于),GE(大于等于)。
取值运算符和属性运算符
OFFSET:可以得到一个标号或变量的偏移地址。例如:mov si,offset data1将变量data1的偏移地址送si。与lea si,data1执行结果相同。
SEG:利用运算符可以得到一个标号或变量的段地址。例如:mov ax,seg data
PTR:属性运算符用来指定定位于其后的存储器操作数的类型。call dword ptr[bx]说明存储器操作数为4个字节,即调用远程。
伪指令
格式
[变量] 伪操作 操作数,[操作数...],常用数据定义伪指令有以下5种。
DB:定义变量为字节类型,变量中的每个操作数占一个字节,DB伪指令也常用来定义字符串。
DW:定义变量为字类型,DW伪指令后面的操作数都占2个字节,在内存中存放时,低字节放到低地址。
DD:定义变量为双字节类型,DD伪指令后面的每个操作数都占用4个字节,在内存中存放时,同样是低字节发放到低地址。
DQ:定义变量为四字类型,在内存中存放。
DT:定义变量为十字节类型,在内存中存放。
操作数
data db 11h,33h ;定义包含两个元素的字节变量data
num dw 100*5+88 ;定义一个字类型,其初值为表达式的值
str db 'hello!' ;定义一个字符串,首地址为str
sum dq 0011223344556677h ;将4个字存入sum,它们在内存中的存放由低到高为77h,66h,55h,44h
data2 dw ? ;为变量data2分配2个字节的空间,初值为任意值
重复操作符
[变量名] 数据定义伪操作 n dup(初值,[初值...]),圆括号中为重复的内容,n为重复的次数,如果用n dup(?)作为数据定义伪操作的唯一操作数,则汇编程序仅保留n个元素大小的数据区。
data1 db 20 dup(?) ;为变量data1分配20个字节的空间,初值为任意值。
data2 db 20 dup(30h) ;为变量data2分配20个字节的空间,初值均为30h
段定义伪指令
段名 segment [定位类型][组合类型]['类别']
...
段名 ends
定位类型
para:说明逻辑段从一个字节的边界开始,16个字节为一个节,所以段的起始地址应能被16整除,也就是起始的物理地址为xxxx0h。
byte:说明逻辑段从字节边界开始,即可以从任何地址开始,此时本段的起始地址紧接在前一个段的后面。
word:说明逻辑段从字边界开始,即本段的起始地址必须是偶数。
page:说明逻辑段从页边界开始,256字节为一页,故本段起始物理地址为xxx00h。
组合类型
none:表示本段与其他逻辑段不组合。即对不同程序模块中的逻辑段,即使具有相同的段名,也分别作为不同的逻辑段装入内存而不进行组合。
public:表示对于不同程序模块中用public说明的具有相同段名的逻辑段,汇编将它们组合到一起,构成一个大的逻辑段。
stack:组合类型为stack时,其含义和public基本相同,但仅限于堆栈段使用。
common:表示不同程序模块中用common说明的同名逻辑段,连接时从同一个地址开始装入,即各个逻辑段重叠一起。连接之后的段长度等于原来最长的逻辑段长度。
memory:表示当几个逻辑段连接时,本逻辑段定位在地址最高的地方。如果被连接的逻辑段中有多个段的组合类型都是memory,则汇编程序只将首先遇到的段作为memory,而其余的都作为common处理。
设定段寄存器伪指令
assume伪指令用于向汇编程序说明所定义的逻辑段属于何种类型的逻辑段。说明方法是将逻辑段的短命和对应的段寄存器联系起来。一般格式为assume 段寄存器名:段名,[段寄存器:段名]。
; 程序功能:10ms延时的子程序
delay proc ;定义一个子过程 proc的作用是子程序定义伪指令
push bx ;保护原有bx内容
push cx ;保存原有cx内容
mov bl,2 ;外循环次数
next: mov cx,4167 ;内循环次数(延时5ms)
w10ms:loop w10ms ;cx不等于0时则循环
dec bl ;修改外循环计数值
jnz next ;BX不为0时则进行第二轮循环
pop cx ;恢复cx值。
pop bx ;恢复bx值。
ret ;过程返回
delay endp