提问,单片机能通过编程易语言执行cmd命令令吗

单片机调用中断服务子程序和调用子程序的区别是什么?
单片机调用中断服务子程序和调用子程序的区别是什么?
09-11-01 &
基于51单片机中断跳出指令“RETI”浅议&   最近在基于51单片机编程的过程中出现了个很奇怪的问题“程序执行中在寄存器EA=1,ET0=1,TR0=1条件下,单TF0=1时并没有执行中断”。在有过单片机中断编程经历者都知道当EA=1,ET0=1的条件下,满足TF0=1时,如果在此期间没有更高优先级的中断执行的情况下定时器中断0必定会产生中断响应。而在我所编写的程序中仅使用了定时器中断0,一个中断也就谈不上存在优先级问题。经过我对自己程序的检查并对各教材中断程序对比发现我的程序中的一个问题:由于中断的不可控性决定其跳出中断返回主程序的不确定,而由于程序需要中断跳出后能跳到指定的地址。为了解决这个问题我在中断结束的地方直接用了无条件跳转指令“LJMPADR16”其中ADR16是我想在中断结束后程序所运行的地址,而没有经过指令“RETI”。问题找到了这就意味着我的程序和其他程序不同的地方就是没有执行“RETI”而直接跳出。/p&   为了解决问题所在我查阅了很多单片机方面的资料,教材。几乎所有的教材对指令“RETI”的作用千篇一律都是:“中断程序完成后,一定要执行一条RETI指令,执行这条指令后,CPU将会把堆栈中保存着的地址取出,送回PC,那么程序就会从主程序的中断处继续往下执行了。”如果“RETI”的作用仅仅在于“把堆栈中保存着的地址取出送回PC”;那么我用指令“POPDPH”和“POPDPL”两条指令取代其做用不就可以达到同样的推出地址的效果么?这样可以解决由于只有进堆栈指令(硬件自动生成)没有出堆栈所导致的堆栈溢出错误,但是并不能解决文章开始所提到的“进不了中断”问题。这让我更加相信书上所介绍的关于指令“RETI”作用并不完全。经过查阅各种资料文献,我发现了个以往在介绍单片机硬件,以及寄存器上教材,老师,没有提及的“‘优先级生效’触发器“的概念。资料指出“根据8051的结构特点,其中断系统中含有两个不可寻址的“优先级生效”触发器。一个用于指出CPU是否正在执行高优先级的中断服务程序,这个触发器为1时,系统将屏蔽所有的中断请求;另一个则指出CPU是否正在执行低优先级中断服务程序,该触发器为1时,将阻止除高优先级以外的一切中断请求。由此可见,若要响应同级甚至是低级中断请求,必须使得该“优先级生效”触发器清零。但该触发器又是不可寻址的,所以无法用软件直接清零。”问题是不是在这里呢?而“优先级生效”触发器清零过程是怎样执行的呢?是在硬件自动执行的那么是在什么时候执行的呢?带着问题我去解决问题。假设我可以将程序满足跳出中断后跳到自己原来指定地址“ADR16”又满足执行指令“RETI”。经过反复思考我用“DEC SP”;“DEC SP”;“MOVDPTR,#ADR16”;“PUSHDPL”;“PUSHDPL”“PUSHDPH”四条指令代替,问题得到了解决。/p&   总结:中断指令“RETI”做为中断跳出指令除了将堆栈中保存着的地址取出,送回PC;使程序从主程序的中断处继续往下执行。的作用外还有将“优先级生效”触发器清零。自己做的程序也是出现了这个错误,由于对“优先级生效”触发器清零,导致第二次进不了中断(相当于同优先级申请)。/p&  后记:在解决这个问题时候我所用的知识是课本上的,而又不完全是课本上的。在这个过程中我用已学的知识解决了自己的问题,并进一步推出中断过程的一些新的知识,我认为新知识的学习有很大程度的要靠自己在已学过知识的基础上通过运用,总结,推导等过程获得新知识。着也是当代大学生运用知识,获取新知识的一种能力。/p
请登录后再发表评论!
在Linux操作系统下有3类主要的设备文件类型:块设备、字符设备和网络设备。这种分类方法可以将控制输入/输出设备的驱动程序与其他操作系统软件分离开来。 字符设备与块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般紧接着发生。块设备则不然,它利用一块系统内存作为缓冲区,若用户进程对设备的请求能满足用户的要求,就返回请求的数据;否则,就调用请求函数来进行实际的I/O操作。块设备主要是针对磁盘等慢速设备设计的,以免耗费过多的CPU时间用来等待。网络设备可以通过BSD套接口访问数据。 每个设备文件都有其文件属性(c/b),表示是字符设备还是块设备。另外每个文件都有2个设备号,第一个是主设备号,标识驱动程序;第二个是从设备号,标识使用同一个设备驱动程序的、不同的硬件设备。设备文件的主设备号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问驱动程序。 系统调用时操作系统内核与应用程序之间的接口,设备驱动程序是操作系统内核与机器硬件之间的接口。设备驱动程序是内核的一部分,它完成以下功能: *对设备初始化和释放 *把数据从内核传送到硬件和从硬件读取数据 *读取应用程序传送给设备文件的数据和回送应用程序请求的数据 *检测和处理设备出现的错误 MTD(Memory Technology Device)设备是闪存芯片、小型闪存卡、记忆棒之类的设备,它们在嵌入式设备中的使用正在不断增加。MTD驱动程序是在Linux下专门为嵌入式环境开发的新的一类驱动程序。相对于常规块设备驱动程序,使用MTD驱动程序的优点在于他们能更好的支持、管理给予闪存设备,有基于扇区的擦除和读/写操作的更好的接口。 驱动程序结构 Linux的设备驱动程序可以分为3个主要组成部分: 1. 自动配置和初始化子程序,负责监测所要驱动的硬件设备是否存在和能否正常工作。如果该设备正常,则对这个设备及其相关的设备驱动程序需要的软件状态进行初始化。这部分驱动程序仅在初始化时被调用一次。 2. 服务于I/O请求的子程序,又称为驱动程序的上半部分。调用这部分程序是由于系统调用的结果。这部分程序在执行时,系统仍认为是与进行调用的进程属于同一个进程,只是由用户态变成了核心态,具有进行此系统调用的用户程序的运行环境,因而可以在其中调用sleep()等与进行运行环境有关的函数。 3. 中断服务子程序,又称为驱动程序的下半部分。在Linux系统中,并不是直接从中断向量表中调用设备驱动程序的中断服务子程序,而是由Linux系统来接收硬件中断,再由系统调用中断服务子程序。中断可以在任何一个进程运行时产生,因而在中断服务程序被调用时,不能依赖于任何进程的状态,也就不能调用任何与进程运行环境有关的函数。因为设备驱动程序一般支持同一类型的若干设备,所以一般在系统调用中断服务子程序时,都带有一个或多个参数,以唯一标识请求服务的设备。 在系统内部,I/O设备的存/取通过一组固定的入口点来进行,这组入口点是由每个设备的驱动程序提供的。具体到Linux系统,设备驱动程序所提供的这组入口点由一个文件操作结构来向系统进行说明。file_operation结构定义于linux/fs.h文件中。 struct file_operation{int (*lseek)(struct inode *inode, struct file *filp, off_t off, int pos);int (*read)(struct inode *inode, struct file *filp, char *buf, int count);int (*write)(struct inode *inode, struct file *filp, const char *buf, int count);int (*readdir)(struct inode *inode, struct file *filp, struct dirent *dirent, int count);int (*select)(struct inode *inode, struct file *filp, int sel_type, select_table *wait);int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned int arg);int (*mmap)(void);int (*open)(struct inode *inode, struct file *filp);int (*release)(struct inode *inode, struct file *filp);int (*fasync)(struct inode *inode, struct file *filp);}; file_operation结构中的成员几乎全部是函数指针,所以实质上就是函数跳转表。每个进程对设备的操作都会根据major、minor设备号,转换成对file_operation结构的访问。 常用的操作包括以下几种: *lseek, 移动文件指针的位置,只能用于可以随机存取的设备。 *read, 进行读操作,参数buf为存放读取结果的缓冲区,count为所要读取的数据长度。返回值为负表示读取操作发生错误;否则,返回实际读取的字节数。对于字符型,要求读取的字节数和返回的实际读取字节数都必须是inode-i_blksize的倍数。 *write, 进行写操作,与read类似 *readdir, 取得下一个目录入口点,只有与文件系统相关的设备程序才使用。 *select, 进行选择操作。如果驱动程序没有提供select入口,select操作会认为设备已经准备好进行任何I/O操作。 *ioctl, 进行读、写以外的其他操作,参数cmd为自定义的命令 *mmap, 用于把设备的内容映射到地址空间,一般只有块设备驱动程序使用 *open, 打开设备准备进行I/O操作。返回0表示打开成功,返回负数表示失败。如果驱动程序没有提供open入口,则只要/dev/driver文件存在就认为打开成功。 *release, 即close操作。 在用户自己的驱动程序中,首先要根据驱动程序的功能,完成file_operation结构中函数实现。不需要的函数接口可以直接在file_operation结构中初始化为NULL。file_operation变量会在驱动程序初始化时注册到系统内部。当操作系统对设备操作时,会调用驱动程序注册的file_operation结构中的函数指针。 Linux对中断的处理 在Linux系统里,对中断的处理是属于系统核心部分,因而如果设别与系统之间以中断方式进行数据交换,就必须把该设备的驱动程序作为系统核心的一部分。设备驱动程序通过调用request_irq函数来申请中断,通过free_irq来释放中断。它们被定义为: #include int request_irq(unsigned int irq, void (*handler)(int irq, void dev_id, struct pt_regs *regs),unsigned long flags,const char *device,void *dev_id);void free_irq(unsigned int irq, void *dev_id); 参数irq表示所要申请的硬件中断号;handler为向系统登记的中断处理子程序,中断产生时由系统来调用,调用时所带参数irq为中断号;dev_id为申请时告诉系统的设备标识;regs为中断发生时的寄存器内容;device为设备名,将会出现在/proc/interrupts文件里;flag是申请时的选项,它决定中断处理程序的一些特性,其中最重要的是中断处理程序是快速处理程序还是慢速处理程序。快速处理程序运行时,所有中断都被屏蔽,而慢速处理程序运行时,除了正在处理的中断外,其他中断都没有被屏蔽。在Linux系统中,中断可以被不同的中断处理程序共享。 作为系统核心的一部分,设备驱动程序在申请和释放内存时不是调用malloc和free,而代之以调用kmalloc和kfree,它们被定义为: #include void *kmalloc(unsigned int len, int priority);void kfree(void *obj); 参数len为希望申请的字节数;obj为要释放的内存指针;priority为分配内存操作的优先级,即在没有足够空闲内存时如何操作,一般用GFP_KERNEL。
请登录后再发表评论!
简单点讲就是子程序可以在程序中通过程序人为调用,什么时候调用都可以;而中断服务程序必须由相应的中断机构来执行,不能人为调用。
请登录后再发表评论!
中断服务子程序入口地址相对某一中断是固定的,调用子程序的偏移地址是随机的,基本过程差不多,返回时,中断服务子程序用的是RETI 调用子程序是RET调用中断服务子程序和调用子程序都会把当前偏移地址的下一地址压栈,但调用子程序的返回指令RET不影响标志位,RETI从中断程序返回,并会清除内部相应的中断状态寄存器。我说的是80C51的
请登录后再发表评论!
基于51单片机中断跳出指令“RETI”浅议&   最近在基于51单片机编程的过程中出现了个很奇怪的问题“程序执行中在寄存器EA=1,ET0=1,TR0=1条件下,单TF0=1时并没有执行中断”。在有过单片机中断编程经历者都知道当EA=1,ET0=1的条件下,满足TF0=1时,如果在此期间没有更高优先级的中断执行的情况下定时器中断0必定会产生中断响应。而在我所编写的程序中仅使用了定时器中断0,一个中断也就谈不上存在优先级问题。经过我对自己程序的检查并对各教材中断程序对比发现我的程序中的一个问题:由于中断的不可控性决定其跳出中断返回主程序的不确定,而由于程序需要中断跳出后能跳到指定的地址。为了解决这个问题我在中断结束的地方直接用了无条件跳转指令“LJMPADR16”其中ADR16是我想在中断结束后程序所运行的地址,而没有经过指令“RETI”。问题找到了这就意味着我的程序和其他程序不同的地方就是没有执行“RETI”而直接跳出。/p&   为了解决问题所在我查阅了很多单片机方面的资料,教材。几乎所有的教材对指令“RETI”的作用千篇一律都是:“中断程序完成后,一定要执行一条RETI指令,执行这条指令后,CPU将会把堆栈中保存着的地址取出,送回PC,那么程序就会从主程序的中断处继续往下执行了。”如果“RETI”的作用仅仅在于“把堆栈中保存着的地址取出送回PC”;那么我用指令“POPDPH”和“POPDPL”两条指令取代其做用不就可以达到同样的推出地址的效果么?这样可以解决由于只有进堆栈指令(硬件自动生成)没有出堆栈所导致的堆栈溢出错误,但是并不能解决文章开始所提到的“进不了中断”问题。这让我更加相信书上所介绍的关于指令“RETI”作用并不完全。经过查阅各种资料文献,我发现了个以往在介绍单片机硬件,以及寄存器上教材,老师,没有提及的“‘优先级生效’触发器“的概念。资料指出“根据8051的结构特点,其中断系统中含有两个不可寻址的“优先级生效”触发器。一个用于指出CPU是否正在执行高优先级的中断服务程序,这个触发器为1时,系统将屏蔽所有的中断请求;另一个则指出CPU是否正在执行低优先级中断服务程序,该触发器为1时,将阻止除高优先级以外的一切中断请求。由此可见,若要响应同级甚至是低级中断请求,必须使得该“优先级生效”触发器清零。但该触发器又是不可寻址的,所以无法用软件直接清零。”问题是不是在这里呢?而“优先级生效”触发器清零过程是怎样执行的呢?是在硬件自动执行的那么是在什么时候执行的呢?带着问题我去解决问题。假设我可以将程序满足跳出中断后跳到自己原来指定地址“ADR16”又满足执行指令“RETI”。经过反复思考我用“DEC SP”;“DEC SP”;“MOVDPTR,#ADR16”;“PUSHDPL”;“PUSHDPL”“PUSHDPH”四条指令代替,问题得到了解决。/p&   总结:中断指令“RETI”做为中断跳出指令除了将堆栈中保存着的地址取出,送回PC;使程序从主程序的中断处继续往下执行。的作用外还有将“优先级生效”触发器清零。自己做的程序也是出现了这个错误,由于对“优先级生效”触发器清零,导致第二次进不了中断(相当于同优先级申请)。/p&  后记:在解决这个问题时候我所用的知识是课本上的,而又不完全是课本上的。在这个过程中我用已学的知识解决了自己的问题,并进一步推出中断过程的一些新的知识,我认为新知识的学习有很大程度的要靠自己在已学过知识的基础上通过运用,总结,推导等过程获得新知识。着也是当代大学生运用知识,获取新知识的一种能力。/p
请登录后再发表评论!AVR 单片机与GCC 编程使用方法
- AVR单片机 - 电子工程世界网
AVR 单片机与GCC 编程使用方法
14:12:11来源: eefocus
WIN 是一个ATMEL AVR 系列的开发工具集,它包含GNU C 和C++编译器。
1.1 一个简单的例子
为了先有一个感性的认识,我们首先看一下如下一段程序和它的编译、链接过程。
文件demo1.c :
int main( void )
unsigned char i, j, k,=0;
PORTB|=0X01;
PORTB&=0XFE;
for (i=0; i<255; i++)
for(j=0; j<255;j++)
这是一个使接在PB0 口的 发光管闪烁的程序。有了源程序文件demo1.c,我们就可以编译它了。通过点击菜单开始->运行 在弹出的对话框中输入 & command & ,来打开控制台窗口,并在命令行输入:-gcc &mmcu=at90s2313 &c demo1.c
如图1-1 所示。
必需告诉编译器 程序的mcu 类型,这是我们通过命令行选项-mmcu 来指定的,我们指定的器件为at90s2313。-c 选项告诉编译器编译完成后不链接。
图1-1 控制台窗口
编译完成后在工作目录新生成了一个文件:demo1.o ,它是我们的目标文件,我们再使用链接器将它链接成可在器件上执行的二进制代码。
在命令行输入:avr-gcc &mmcu=at90s2313 &O demo1.elf demo1.o
之后我们会在工作目录看见链接器生成的demo1.elf。gcc 的链接后生成的文件为ELF 格式,在命令行我们通常用.elf 指定其扩展名。ELF 格式文件除了包含不同存储器的二进制格式内容外还包含一些调试信息,所以我们还要借助一个有用工具 avr-objcopy 来提取单片机内容。命令行输入:avr-objcopy -j .text -j .data -O ihex demo1.elf demo1.hex
gcc 把不同类型的数据分到不同的段落,相关程序存储器的段有 .text 和 .data ,我们用选项 &j 指定了要提取的段。选项 &O 用来指定输出格式,这里我们指定为ihex (intel HEX file)。
到此我们得到了最终可以写入单片机90S2313 FLASH 存储器的demo1.hex 文件。用编程器将demo1.hex 内空写入到单片机,便可看到接在PB0 口的LED 不断的闪烁。以上对一次编译过程的描述只是为了说明gcc 编译一个C 源程序的步骤,在实际的应用中我们很少用这种方式编译每一个源程序和每一个更新后的程序。而是借助一个叫make 的项目管理工具来进行编译操作。Make 由下一节介绍。
1.2 用MAKEFILE 管理项目
在我看来,通常一个编译器(泛指高级语言编译器、汇编器、链接器等等)、项目管理器和文本编辑器构成一个完整的编程环境。
WINAVR 没有像Keil uVision 那样的集成IDE,所以我们需要写一个叫做makefile 的文件来管理程序的编译链接。makefile 是个脚本文件,一个标准的(应该说经典的)可执行文件make.exe 负责解析它并根据脚本内容来调用编译器、链接器或其它的工具。
1.2.1 make 的使用
make 能够自动记忆各源文件间的依赖关系,避免重复编译。
Make 指令用法是:
Make [-f filename] [names]
方括号表示括号里边的内容可以省略。其中filename 代表make 所使用的项目描述文件,如果此项省略,则从当前目录下按下列顺序寻找默认的项目描述文件
GNUmakefile.
Makefile (当然在WINDOWS 下不份大小写文件名,也就无所谓了)
names 指定目标名或宏名。若不指定目标名,则make 命令总是把在makefile 文件中遇到的第一个目标当作默认目标。
1.2.2 Makefile 项目描述文件
make 命令引入了目标(targets)的概念。Makefile 描述文件便是它的第一个目标,make 命令必须处理至少一个目标,否则不会得出任何结果。正如我们在一个没有默认描述文件的当前目录下敲入make 一样,make 会输出以下的结果:
MAKE: ***No targets specifiend no makefile found. Stop.
1.在项目描述文件中定义目标
一个目标通常从一行的开头开始,并后跟一个冒号。
最简单的MAKEFILE
#当前目录 D:\AVRGCC\TEST
@echo hello!
#End makefile
all: 便是第一个目标
调用此描述文件结果:
D:\AVRGCC\TEST>make
2.默认目标(goal)
在上面提到过,如果调用make 时不指定目标名则make 总是假设在描述文件中遇到的第一个目标是默认目标。以下示例可以非常好的说明这一问题。
具有三个目标的makefile
#当前目录 D\AVRGCC\TEST
@echo one.
@echo two.
@echo three.
#End makefile
D:\AVRGCC\TEST>make
由于在命令行没有指定目标,make 从makefile 中找到第一个目标(one)并执行后既退出。
D:\AVRGCC\TEST>make two
由于在命令行明确指定了要执行的目标(two),make 在makefile 中找到指定的目标,并执行后退出。
D:\AVRGCC\TEST make three one two
命令行指定了三个目标,make 一一寻找并执行。
在makefile 中非默认的目标称为可替换的目标,只有默认的目标与它们存在直接或间接的依赖关系时它们才有可能被调用。
二.依赖关系
makefile 文件按如下格式指定依赖关系:
目标1[目标2 & ] : [ : ][依赖1][依赖2] &
#当前目录 D:\AVRGCC\TEST
@echo one.
@echo two.
#End makefile
执行结果是:
d:\avrgcc\test>make
Make 首先找到第一个目标one ,之后发现目标one 依赖目标Two 就先执行Two 后才执行one 中的命令。
三.Makefile 内容
makefile 内容可分为如下五种类型
①规则定义
目标 : 依赖
其中目标为一个文件名或以空格分开的多个文件名,可含通配符。
avr-gcc -c $< -o $@
以上规则定义了任意一个以 .o 结尾的文件依赖于相同前缀且以 .c 结尾的文件。并执行下边的命令获得。
规则中目标和依赖分别为 %.o 和%.c,在目标通配符 &%& 代表任意的字符串,而在依赖中代表与目标中代表的对应字符串。
②隐含规则
隐含规则是make 预先定义的规则,用选项 &r 可取消所有的隐含规则。
例如对于C 程序 %.o 可以自动的从 %.c 通过命令
$(CC) -c $(CPPFLAGS) $(CFLAGS)&#39; 生成。
变量是在makefile 中描述一字符串的的名称。变量可用在目标、依赖、命令和makefile 其它部分中。变量名由除&#39;: &#39;、&#39;#&#39;、&#39;=&#39;之外的字符组成,对大小写敏感。
变量的定义并赋值格式:
变量名 = 变量代表字符串
变量的引用格式:
CC = avr-gcc
$(CC) -c $< -o $@
命令部分是由make 传递到系统的字符格式的命令行的组合,在目标被创建时它们按顺序一行一行传递到系统并执行。
字符 &#39;@&#39;开始的命令 在系统的输出中不显示本次的指令行。
字符 &#39;# &#39; 开头的行为注释行,如果注释需要换行需在行尾加 &#39; \ &#39;,除包含其它MAKEFIEL 外在行的任意处可插入注释。
四.自动变量
在makefile 中有一组预定义的变量,当每一规则被执行时根据目标和依赖重新计算其值,叫作自动变量。
下面列出了常用的几个自动变量
$@ : 在规则中代表目标名,如果规则含有多个目标名它将列举所有目标。
$% : 仅在目标是存档文件的成员时起作用,代表目标。
如目标foo.a(bar.o)中$@ 代表foo.a $%代表bar.o
$< : 在规则中代表第一个依赖文件名
$? : 代表在规则中所有以空格隔开的依赖文件名,如果依赖是存档文件的成员则只有成员名被列出。
$^ : 代表在规则中所有以空格隔开的依赖文件名,如果依赖是存档文件的成员则只有成员名被列出。
WINAVR 提供一种简单makefile 生成工具叫mfile,如图1-2
利用它我们可方便的生成合适的makefile。
Main file name&菜单指定主程序文件,它将决定主源程序文件名及输出文件名。
Output format 菜单项用于选择最终生成的可指行代码格式,根据编程器支持格式选择即可。
Optimization leave 指定C 代码的优化级,s 代表按最小代码量编译。
C/C++ source file(s) 和Assembler source files(s) 用于在项目中添加其它C、C++、和汇编程序文件。
图1-2 mfile 生成makefile
通常我们选择了以上几项便可编译了。
1.3 开发环境的配置
一.软件环境
UltraEdit + WinAVR 打造超级开发IDE
UltraEdit 是个文本编辑器,它支持C 代码的高亮显示、项目管理及外部工具配置等功能。
首先要安装UltraEdit 和 WinAVR。
(1) UltraEdit 的个性化设置:
下面是我个人习惯的设置
视图->颜色设置 光标所在行文本 设置成黑,光标所在行背景设置成白
高级->配置->编辑 制表符宽度值和缩进空格娄均设成4。
高级->配置->备份 保存时备份文件里选择不备份。
视图->查看列表 选中函数列表
(2) 创建编译用的文件
先在硬盘上创建一个目录作为设计大本营,这里假设为 d:\devdir
UltraEdit 写主程序文件保存到此文件夹中 这里设为demo.c
用mfile 生成一个合适的makefile 保存到d:\devdir
UltraEdit 创建一项目,负责管理文件
项目->新建项目 目录选d:\devdir 输入项目名称(这里假设为prj)
在接下来的文件设置对话框中的项目文件目录区输入或选择d:\devdir
选中 相对路径 复选按钮
通过 添加文件 按钮将刚才的makefile 和demo.c 添加到项目中,之后按关闭。
(3)在UltraEdit 中make 我的项目
高级 -> 工具配置
在命令行区输入 make
在工作目录区输入 d:\devdir
在菜单项目名称区输入一个任意的菜单名称
选中 输出到列表框 和 捕获输出两个选择按钮后单击 插入按钮 确定。
至此你就可以在UltraEdit 内就可以make 你的程序了?
如果不愿意每次编译时找菜单可用快捷键 Ctrl+shift+0。
记得要在你的项目里添加源程序时,除了在UltraEdit 项目->文件设置里添加外还要在
makefile 的SRC 变量后列出来才可编译哦?
到此 我们的超级无敌AVR 编程环境打造完成 ,如图1-3。
图 1-3 配置后的UltraEdit 外观
二.硬件环境
SI-Prog + PonyProg 实现最廉价的下载实验器 AVR 系列单项机提供对程序存储器(FLASH)和数据存储器(EEPROM)的串行编程功能(ISP),使它的程序烧写变得方便。AVR 系列器件内部FLASH 存储器的编程次数通常可达到10000 次以上,所以使用多次烧写的方式调试程序时不必担心器件的损坏。
ISP 功能占用三个I/O 端口(MOSI 、MISO、 SCK)与外部编程逻辑通信,编程逻辑按指定时序将程序数据串行方式发送到器件,器件内的ISP 功能模块负责将数据写入到FLASH 或EEPROM。
在实际应用中通常利用PC 机的并行口或串行口加一个下载适配器(下载线)实现一个编程硬件,AVR 的下载线有很多种,这里向大家推荐SI-Prog,SI-Prog 具有制作方便(只需几个分立元件)、接线少(通过PC 9 针串行口编程),支持软件成熟(PonyProg)等特点。 si-prog 的完整电路可到 下载。图1-4
为简化后的图。
PonyPorg 是个串行器件编程软件,支持AVR 在内的多种器件的串行编程。该软件可到 下载。
图1-4 SI-Prog 电路原理图
有了一台安装有PonyPorg 的PC 机和SI-Prog ,就可以将程序写入到实际器件来验证了,想一想此方案的成本和一个AVR 芯片能烧写的次数,是不是觉得很值?
读到这里您对AVR 单片机的开发和WINAVR 编程应该有了一个基本的认识,也应当做好了开发或学习前软硬件的准备工作。从下一章开始我将进一步解析AVR 的GCC 程序设计。
第二章 存储器操作
2.1 AVR 单片机存储器组织结构
AVR 系列单片机内部有三种类型的被独立编址的存储器,它们分别为:Flash 程序存储器、内部SRAM 数据存储器和EEPROM 数据存储器。
Flash 存储器为1K~128K 字节,支持并行编程和串行下载,下载寿命通常可达10,000 次。
由于AVR 指令都为16 位或32 位,程序计数器对它按字进行寻址,因此FLASH 存储器按字组织的,但在程序中访问FLASH 存储区时专用指令LPM 可分别读取指定地址的高低字节。
寄存器堆(R0~R31)、I/O 寄存器和SRAM 被统一编址。所以对寄存器和I/O 口的操作使用与访问内部SRAM 同样的指令。其组织结构如图2-1 所示。
图2-1 AVR SRAM 组织
32 个通用寄存器被编址到最前,I/O 寄存器占用接下来的64 个地址。从0X0060 开始为内部SRAM。外部SRAM 被编址到内部SRAM 后。
AVR 单片机的内部有64~4K 的EEPROM 数据存储器,它们被独立编址,按字节组织。擦写寿命可达100,000 次。
2.2 I/O 寄存器操作
I/O 专用寄存器(SFR)被编址到与内部SRAM 同一个地址空间,为此对它的操作和SRAM 变量操作类似。
SFR 定义文件的包含:
io.h 文件在编译器包含路径下的avr 目录下,由于AVR 各器件间存在同名寄存器地址有不同的问题,io.h 文件不直接定义SFR 寄存器宏,它根据在命令行给出的 &mmcu 选项再包含合适的 ioxxxx.h 文件。
在器件对应的ioxxxx.h 文件中定义了器件SFR 的预处理宏,在程序中直接对它赋值或引用的方式读写SFR,如:
PORTB=0XFF;
从io.h 和其总包含的头文件_defs.h 可以追溯宏PORTB 的原型
在io2313.h 中定义:
#define PORTB _SFR_IO8(0x18)
在sfr_defs.h 中定义:
#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + 0x20)
#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))
这样PORTB=0XFF; 就等同于 *(volatile unsigned char *)(0x38)=0
0x38 在器件AT90S2313 中PORTB 的地址
对SFR 的定义宏进一步说明了SFR 与SRAM 操作的相同点。
关键字volatile 确保本条指令不会因C 编译器的优化而被省略。
2.3 SRAM 内变量的使用
一个没有其它属性修饰的C 将被指定到内部SRAM,avr-libc 提供一个整数类型定义文件inttype.h,其中定义了常用的整数类型如下表:
定义值 长度(字节) 值范围
int8_t 1 -128~127
uint8_t 1 0~255
int16_t 2 -3
uint16_t 2 0~65535
int32_t 4 -~
uint32_t 4 0~
int64_t 8 -9.22*10^18~-9.22*10^18
uint64_t 8 0~1.844*10^19
根据习惯,在程序中可使用以上的整数定义。
定义、初始化和引用
如下示例:
uint8_t val=8; 定义了一个SRAM 变量并初始化成8
val=10; 改变变量值
const uint8_t val=8; 定义SRAM 区常量
register uint8_t val=10; 定义
2.4 在程序中访问FLASH 程序存储器
avr-libc 支持头文件:pgmspace.h
在程序存储器内的数据定义使用关键字 __attribute__((__progmem__))。在pgmspace.h
中它被定义成符号 PROGMEM。
1. FLASH 区整数常量应用
定义格式:
数据类型 常量名 PROGMEM = 值 ;
char val8 PROGMEM = 1 ;
int val16 PROGMEM = 1 ;
long val32 PROGMEM =1 ;
对于不同长度的整数类型 avr-libc 提供对应的读取函数:
pgm_read_byte(prog_void * addr)
pgm_read-word(prg_void *addr)
pgm_read_dword(prg_void* addr)
另外在pgmspace.h 中定义的8 位整数类型 prog_char prog_uchar 分别指定在FLASH
内的8 位有符号整数和8 位无符号整数。应用方式如下:
char ram_ //ram 内的变量
const prog_char flash_val = 1; //flash 内常量
ram_val=pgm_read_byte(&flash_val); //读flash 常量值到RAM 变量
对于应用程序FLASH 常量是不可改变的,因此定义时加关键字const 是个好的习惯。
2. FLASH 区数组应用:
const prog_uchar flash_array[] = {0,1,2,3,4,5,6,7,8,9}; //定义
另外一种形式
const unsigned char flash_array[] RROGMEM = {0,1,2,3,4,5,6,7,8,9};
读取示例:
unsigend char I, ram_
for(I=0 ; I<10 ;I ++) // 循环读取每一字节
ram_val = pgm_read_byte(flash_array + I);
& & //处理
2. FLASH 区字符串常量的应用
全局定义形式:
const char flash_str[] PROGMEM = &Hello, world!&;
函数内定义形式:
const char *flash_str = PSTR(&Hello, world!&);
以下为一个FLASH 字符串应用示例
const char flash_str1[] PROGMEM = &全局定义字符串&;
int main(void)
char *flash_str2=PSTR(&函数内定义字符串&);
scanf(&%d&,&I);
printf_P(flash_str1);
printf(&\n&);
printf_P(flash_str2);
printf(&\n&);
2.5 EEPROM 数据存储器操作
#include EEPROM.h>
头文件声明了avr-libc 提供的操作EEPROM 存储器的API 函数。
这些函数有:
EEPROM_is_ready() //EEPROM 忙检测(返回EEWE 位)
EEPROM_busy_wait() //查询等待EEPROM 准备就绪
uint8_t EEPROM_read_byte (const uint8_t *addr) //从指定地址读一字节
uint16_t EEPROM_read_word (const uint16_t *addr) //从指定地址一字
void EEPROM_read_block (void *buf, const void *addr, size_t n) //读块
void EEPROM_write_byte (uint8_t *addr, uint8_t val) //写一字节至指定地址
void EEPROM_write_word (uint16_t *addr, uint16_t val) //写一字到指定地址
void EEPROM_write_block (const void *buf, void *addr, size_t n)//写块
在程序中对EEPROM 操作有两种方式
方式一:直接指定EERPOM 地址
/*此程序将0xaa 写入到EEPROM 存储器 0 地址处,
再从0 地址处读一字节赋给RAM 变量val */
#include EEPROM.h>
int main(void)
EEPROM_busy_wait(); //等待EEPROM 读写就绪
EEPROM_write_byte(0,0xaa); //将0xaa 写入到EEPORM 0 地址处
EEPROM_busy_wait();
val=EEPROM_read_byte(0); //从EEPROM 0 地址处读取一字节赋给RAM 变量val
方式二:先定义EEPROM 区变量法
#include EEPROM.h>
unsigned char val1 __attribute__((section(".EEPROM")));//EEPROM 变量定义方式
int main(void)
unsigned char val2;
EEPROM_busy_wait();
EEPROM_write_byte (&val1, 0xAA); /* 写 val1 */
EEPROM_busy_wait();
val2 = EEPROM_read_byte(&val1); /* 读 val1 */
在这种方式下变量在EEPROM 存储器内的具体地址由编译器自动分配。相对方式一,数据在EEPROM 中的具体位置是不透明的。
为EEPROM 变量赋的初始值,编译时被分配到.EEPROM 段中,可用avr-objcopy 工具从.elf文件中提取并产生ihex 或binary 等格式的文件。
2.6 avr-gcc 段(section)与再定位(relocation)
粗略的讲,一个段代表一无缝隙的数据块(地址范围),一个段里存储的数据都为同一性质,如&只读&数据。as (汇编器)在编译局部程序时总假设从0 地址开始,并生成目标文件。最后ld(链接器)在连接多个目标文件时为每一个段分配运行时(run-time)统一地址。这虽然是个简单的解释,却足以说明我门为为什么用段.
ld 将这些数据块正确移动到它们运行时的地址。 此过程非常严格,数据的内部顺序与长度均不能发生变化.这样的数据单元叫做段,为段分配运行时地址叫再定位,此任务根据目标文件内的参考地址将段数据调整到运行时地址。
Avr-gcc 中汇编器生成的目标文件(object-file)至少包含四个段,分别为: .text 段、.data段 、 .bss 段和.EEPROM 段,它们包括了程序存储器(FLASH)代码,内部RAM 数据,和EEPROM 存储器内的数据。这些段的大小决定了程序存储器(FLASH)、数据存储器(RAM)、EEPROM 存储器的使用量,关系如下:
程序存储器(FLASH)使用量 = .text + .data
数据存储器(RAM)使用量 = .data + .bss [+ .noinit] + stack [+ heap]
EEPROM 存储器使用量 = .EEPROM
一..text 段
.text 段包含程序实际执行代码。另外,此段还包含.initN 和.finiN 两种段,下面详细讨论。
段.initN 和段.finiN 是个程序块,它不会象函数那样返回,所以汇编或C 程序不能调用。
.initN、.finN 和绝对段(absolute section 提供中断向量)构成avr-libc 应用程序运行框架,用户编写的应用程序在此框架中运行。
此类段包含从复位到main()函数开始执行之间的启动(stap)代码。
此类段共定义10 个分别是.init0 到.init9。执行顺序是从.init0 到.init9。
此段绑定到函数__init()。用户可重载__init(),复位后立即跳到该函数。
未用,用户可定义
初始化堆栈的代码分配到此段
未用,用户可定义
初始化.data 段(从FLASH 复制全局或静态变量初始值到.data),清零.bss 段。
像UNIX 一样.data 段直接从可执行文件中装入。Avr-gcc 将.data 段的初始值存储到flash
rom 里.text 段后,.init4 代码则负责将这些数据复制SRAM 内.data 段。
未用,用户可定义
C 代码未用,C++程序的构造代码
未用,用户可定义
未用,用户可定义
跳到main()
avr-libc 包含一个启动模块(startup module),用于应用程序执行前的环境设置,链接时它被分配到init2 和init4 中,负责提供缺省中断程序和向量、初始化堆栈、初始化.data 段和清零.bss 段等任务,最后startup 跳转到main 函数执行用户程序。
此类段包含main()函数退出后执行的代码。
此类段可有0 到9 个, 执行次序是从fini9 到 fini1。
此段绑定到函数exit()。用户可重载exit(),main 函数一旦退出exit 就会被执行。
未用,用户可定义
未用,用户可定义
C 代码未用, C++程序的析构代码
未用,用户可定义
未用,用户可定义
未用,用户可定义
未用,用户可定义
未用,用户可定义
进入一个无限循环。
用户代码插入到.initN 或.finiN
示例如下:
void my_init_portb (void) __attribute__ ((naked)) \
__attribute__ ((section (".init1")));
void my_init_portb (void)
outb (PORTB, 0xff);
outb (DDRB, 0xff);
由于属性section(&.init1&)的指定,编译后函数my_init_portb 生成的代码自动插入到.init1段中,在main 函数前就得到执行。naked 属性确保编译后该函数不生成返回指令,使下一个初始化段得以顺序的执行。
二..data 段
.data 段包含程序中被初始化的RAM 区全局或静态变量。而对于FLASH 存储器此段包含在程序中定义变量的初始化数据。类似如下的代码将生成.data 段数据。
char err_str[]=&Your program has died a horrible death!&;
struct point pt={1,1};
可以将.data 在SRAM 内的开始地址指定给连接器,这是通过给avr-gcc 命令行添加
-Wl,-Tdata,addr 选项来实现的,其中addr 必须是0X800000 加SRAM 实际地址。例如 要将.data 段从0x1100 开始,则addr 要给出0X801100。
三..bss 段
没有被初始化的RAM 区全局或静态变量被分配到此段,在应用程序被执行前的startup过程中这些变量被清零。
另外,.bss 段有一个子段 .noinit , 若变量被指定到.noinit 段中则在startup 过程中不会被清零。将变量指定到.noinit 段的方法如下:
int foo __attribute__ ((section (&.noinit&)));
由于指定到了.noinit 段中,所以不能赋初值,如同以下代码在编译时产生错误:
int fol __attribute__((section(&.noinit&)))=0x00
四..EEPROM 段
此段存储EEPROM 变量。
Static unsigned char eep_buffer[3] __attribute__((section(&.EEPROM&)))={1,2,3};
在链接选项中可指定段的开始地址,如下的选项将.noinit 段指定位到RAM 存储器
0X2000 地址处。
avr-gcc ... -Wl,--section-start=.noinit=0x802000
要注意的是,在编译时Avr-gcc 将FLASH、RAM 和EEPROM 内的段在一个统一的地址空间内处理,flash 存储器被定位到0 地址开始处,RAM 存储器被定位到0x800000 开始处,EEPROM 存储器被定位到0X810000 处。所以在指定段开始地址时若是RAM 内的段或EEPROM 内的段时要在实际存储器地址前分别加上0x800000 和0X810000。
除上述四个段外,自定义段因需要而可被定义。由于编译器不知道这类段的开始地址,又称它们为未定义段。必需在链接选项中指定自定义段的开始地址。如下例:
void MySection(void) __attribute__((section(".mysection")));
void MySection(void)
printf("hello avr!");
链接选项:
avr-gcc ... -Wl,--section-start=.mysection=0x001c00
这样函数MySection 被定位到了FLASH 存储器0X1C00 处。
第三章 功能模块编程示例
3.1 中断服务程序
avr-gcc 为中断提供缺省的入口例程,这些例程的名字已固定,用户可通过重载这些例程来处理中断。如果中断没有被用户重载,说明正常情况下不会产生该中断,缺省的中断例程将程序引导到0 地址处(既复位)。
Avr-gcc 为重载中断例程提供两个宏来解决细节的问题,它们是 SIGNAL(signame)和INTERRUPT(signame)。参数signame 为中断名称,它的定义在io.h 中包含。表3-1 列出了ATMega8 的signame 定义,其它器件的signame 定义可查阅相应的ioxxxx.h 文件。
表3-1 ATMega8 中断名称定义
signame 中 断 类 型
SIG_INTERRUPT0 外部中断INT0
SIG_INTERRUPT1 外部中断INT1
SIG_OUTPUT_COMPARE2 定时器/计数器比较匹配中断
SIG_OVERFLOW2 定时器/计数器2 溢出中断
SIG_INPUT_CAPTURE1 定时器/计数器2 输入捕获中断
SIG_OUTPUT_COMPARE1A 定时器/计数器1 比较匹配A
SIG_OUTPUT_COMPARE1B 定时器/计数器1 比较匹配B
SIG_OVERFLOW1 定时器/计数器1 溢出中断
SIG_OVERFLOW0 定时器/计数器0 溢出中断
SIG_SPI SPI 操作完成中断
SIG_UART_RECV USART 接收完成
SIG_UART_DATA USART 寄存器空
SIG_UART_TRANS USART 发送完成
SIG_ADC ADC转换完成
SIG_EEPROM_READY E2PROM 准备就绪
SIG_COMPARATOR 模拟中断
SIG_2WIRE_SERIAL TWI 中断
SIG_SPM_READY 写程序存储器准备好
以下是个外部中断0 的重载示例:
SIGNAL(SIG_INTERRUPT0)
//中断处理程序
宏INTERRUPT 的用法与SIGNAL 类似,区别在于SIGNAL 执行时全局中断触发位被清除、其他中断被禁止,INTERRUPT 执行时全局中断触发位被置位、其他中断可嵌套执行。
另外avr-libc 提供两个API 函数用于置位和清零全局中断触发位,它们分别是:
void sei(void) 和 void cli(void)。
3.2 定时器/计数器应用
下面以定时器/计数器0 为例,说明定时器计数器的两种操作模式
定时器/计数器0 相关寄存器:
TCCR0 :定时器/计数器0 控制寄存器
计数使能,时钟源选择和CPU 时钟预分频设置
TCNT0 :定时器/计数器0 计数值寄存器
包含计数值 (0~255)
TIFR :标志寄存器(Timer Interrupt Flag Register)
TOV0 位 为定时器/寄存器0 溢出标志
TIMSK :定时器中断屏蔽寄存器(Timer Interrupt Mask Register)
TOIE0 位为定时器/寄存器0 中断使能/禁止控制位
查询模式举例:
/* mcu:AT90S2313 时钟:4MHz */
#define uchar unsigned char
#define SET_LED PORTD&=0XEF //PD4 接发光管
#define CLR_LED PORTD|=0X10
int main(void)
uchar i,j=0;
DDRD=0X10;
PORTD=0X10;
TCNT0=0; // T/C0 开始值
TCCR0=5; // 预分频 ck/1024 ,计数允许
//查询定时器方式等待一秒
//24 /256 /15 & 1Hz
for(i=0;i<15;i++)
loop_until_bit_is_set(TIFR,TOV0);
sbi(TIFR,TOV0);//写入逻辑1 清零TOV0 位
if(j) //反向LED 控制脚
SET_LED,j=0;
CLR_LED,j=1;
中断模式举例:
/* mcu:AT90S2313 时钟:4MHz */
#define uchar unsigned char
#define SET_LED PORTD&=0XEF //PD4 接发光管
#define CLR_LED PORTD|=0X10
static uchar g_bCount=0; //中断计数器
static uchar g_bDirection=0;
//T/C0 中断例程
SIGNAL(SIG_OVERFLOW0)
// 产生中断周期 T = 256 * 1024 / 4MHz
if(++g_bCount >14) //中断15 次约一秒
if(g_bDirection) //反向LED 控制脚
SET_LED,g_bDirection=0;
CLR_LED,g_bDirection=1;
g_bCount=0;
int main(void)
DDRD=0X10;
PORTD=0X10;
TCNT0=0; // T/C0 开始值
TCCR0=5; // 预分频 ck/1024 ,计数允许
TIMSK=_BV(TOIE0);
3.3 看门狗应用
avr-libc 提供三个API 支持对器件内部Watchdog 的操作,它们分别是:
wdt_reset() // Watchdog 复位
wdt_enable(timeout) // Watchdog 使能
wdt_disable() // Watchdog 禁止
调用上述函数前要包含头文件 wdt.h ,wdt.h 中还定义Watchdog 定时器超时符号常量,它们用于为wdt_enable 函数提供timeout 值。符号常量分别如下:
符号常量 值含意
WDTO_15MS Watchdog 定时器15 毫秒超时
WDTO_30MS Watchdog 定时器30 毫秒超时
WDTO_60MS Watchdog 定时器60 毫秒超时
WDTO_120MS Watchdog 定时器120 毫秒超时
WDTO_250MS Watchdog 50 毫秒超时
WDTO_500MS Watchdog 定时器500 毫秒超时
WDTO_1S Watchdog 定时器1 秒超时
WDTO_2S Watchdog 定时器2 秒超时
Watchdog 测试程序:
/* mcu:AT90S2313 时钟:4MHz */
#define uchar unsigned char
#define uint unsigned int
#define CLR_LED PORTD&=0XEF //PD4 接发光管
#define SET_LED PORTD|=0X10
//误差不会太大的延时1ms 函数
void DelayMs(uint ms)
for(i=0;i<MS;I++)
_delay_loop_2(4 *250);
int main(void)
DDRD=0X10;
PORTD=0X10; //SET_LED
wdt_enable(WDTO_1S);
wdt_reset();
DelayMs(500);
DelayMs(5000);//等待WDT 复位
wdt_reset();
执行结果:
接在PD4 脚下的LED 不断的闪烁,证明了Watchdog 使mcu 不断的复位。
3.4 UART 应用
/* mcu:AT90S2313 时钟:4MHz */
#define uchar unsigned char
#define uint unsigned int
// 发送一字节数据
void putc(uchar c)
loop_until_bit_is_set(UCR,UDRE);
//uart 等待并接收一字节数据
uchar getc(void)
loop_until_bit_is_set(UCR,RXC);
return UDR;
int main(void)
//uart 初始化
UCR=(1<<RXEN)|(1<<TXEN);
UBRR=25; //baud=9600 UBRR=CK/(baud*16) -1
putc(getc());
程序从UART 等待接收一字节,接收到数据后立即将数据又从UART 发送回去。
中断方式:
/* mcu:AT90S2313 时钟:4MHz */
#define uchar unsigned char
#define uint unsigned int
uchar g_bTxdPos=0; //发送定位计数器
uchar g_bTxdLen=0; //等待发送字节数
uchar g_bRxdPos=0; //接收定位计数器
uchar g_bRxdLen=0; //等待接收字节数
uchar g_aSendBuf[16]; //发送数据绶冲区
uchar g_aRecvBuf[16]; //接收数据缓冲区
//接收中断
SIGNAL(SIG_UART_RECV)
uchar c=UDR;
if(g_bRxdLen>0)
g_aRecvBuf[g_bRxdPos++]=c;
g_bRxdLen--;
//发送中断
SIGNAL (SIG_UART_TRANS)
if(--g_bTxdLen>0)
UDR=g_aSendBuf[++g_bTxdPos];
//是否接收完成
uchar IsRecvComplete(void)
return g_bRxdLen==0;
//从发送缓冲区发送指定长度数据
void SendToUart(uchar size)
g_bTxdPos=0;
g_bTxdLen=
UDR=g_aSendBuf[0];
while(g_bTxdLen>0);
//接收指定长度数据到接收缓冲区
void RecvFromUart(uchar size,uchar bwait)
g_bRxdPos=0;
g_bRxdLen=
while(g_bRxdLen>0);
int main( void )
//uart 初始化
//接收使能、发送使能、接收中断允许、发送中断允许
UCR=(1<<RXCIE)|(1<<TXCIE)|(1<<RXEN)|(1<<TXEN);
UBRR=25; // baud=9600 UBRR=CK/(baud*16) -1
sei();//总中断允许
//异步接收16 字节数据
RecvFromUart(16,0);
//等待接收完成
while(!IsRecvComplete());
//将接收到的数据复制到发送缓冲区
for(i=0;i<16;i++)
g_aSendBuf[i]=g_aRecvBuf[i];
//发送回接收到的数据
SendToUart(16);
利用中断可实现数据的异步发送和接收,正如上面程序所示,调用RecvFromUart 后主程序可处理其它任务,在执行其它任务时可调用IsRecvComplete 检测是否接收完成。
3.5 PWM 功能编程
avr-libc PWM 测试程序
mcu:at90S2313
#define uchar unsigned char
#define uint unsigned int
#define FREQ 4
void DelayMs(uint ms)
for(i=0;i<MS;I++)
_delay_loop_2(FREQ * 250);
int main (void)
uchar direction=1;
uchar pwm=0;
// 8 位PWM 模式 , 向上计数时匹配清除OC1
TCCR1A = _BV (PWM10) | _BV (COM1A1);
//PWM 引脚PB3 方向设置为输出
DDRB= _BV (PB3);
//启动PWM 时钟源:CK/8 PWM 频率为 4MHz/8/512=976Hz
TCCR1B = _BV (CS11);
//循环改变PWM 输出脉宽,使接在OC1 引脚上的发光管亮度发生变化
if(direction)
if(++pwm==254)
direction=0;
if(--pwm==0)
direction=1;
DelayMs(10);
3.6 模拟比较器
模拟比较器测试程序
mcu:ATMega8
时钟:内部4MHz RC 振荡器
#define uchar unsigned char
#define SET_RED_LED PORTB&=0XFD //PB1 接红色发光管
#define CLR_RED_LED PORTB|=0X02
#define SET_YEL_LED PORTB&=0XFE //PB0 接黄色发光管
#define CLR_YEL_LED PORTB|=0X01
//模拟比较器中断函数
SIGNAL(SIG_COMPARATOR)
if(ACSR & _BV(ACO))
SET_YEL_LED;
CLR_RED_LED;
CLR_YEL_LED;
SET_RED_LED;
int main(void)
DDRB=0X03;
PORTB=0X03;
//模拟比较器上下均触发中断 ACIS1=ACIS0=0
//中断允许 ACIE=1
ACSR=_BV(ACIE);
//AIN0:正极 AIN1:负极 AIN0 脚上的电压高于AIN1 上电压时AC0=1
if(ACSR & _BV(ACO))
SET_YEL_LED;
CLR_RED_LED;
CLR_YEL_LED;
SET_RED_LED;
以上程序实现了用LED 指示ATMega8 比较输入引脚 AIN0 和AIN1 上的电压的高低状态。
3.7 A/D 转换模块编程
查询方式:
/* 查询方式 A/D 转换测试程序 mcu:atmega8 时钟:4MHz 外部晶振 */
#define uchar unsigned char
#define uint unsigned int
static uint g_aAdValue[8]; //A/D 转换缓冲区
void IoInit(void);
uint AdcConvert(void)
uchar max_id,min_id,max_value,min_
ADMUX=0Xc0;//内部2.56V 参考电压,0 通道
ADCSRA=_BV(ADEN);//使能ADC,单次转换模式
//连续转换8 次
for(i=0;i<8;i++)
ADCSRA|=_BV(ADSC);
_delay_loop_1(60);
while(ADCSRA&_BV(ADSC))
_delay_loop_1(60);
ret|=(uint)(ADCH<<8);
g_aAdValue[i]=
for(i=1;i<8;i++)
ret+=g_aAdValue[i];
//找到最大和最小值索引
max_id=min_id=1;
max_value=min_value=0;
for(i=1;i<8;i++)
if(g_aAdValue[i]>ret)
if(g_aAdValue[i]-ret>max_value)
max_value=g_aAdValue[i]-
if(ret-g_aAdValue[i]>min_value)
min_value=ret-g_aAdValue[i];
//去掉第一个和最大最小值后的平均值
for(i=1;i<8;i++)
if((i!=min_id)&&(i!=max_id))
ret+=g_aAdValue[i];
if(min_id!=max_id)
ADCSRA=0;//关闭ADC
int main(void)
scanf("%c",&i);
if(i==&#39;c&#39;)
printf("%d\n",AdcConvert());
中断方式:
/* 中断方式 A/D 转换测试程序 mcu:atmega8 时钟:4MHz 外部晶振 */
#define uchar unsigned char
#define uint unsigned int
static uint g_nAdValue=0;
void IoInit(void);
// A/D 转换完成中断
SIGNAL(SIG_ADC)
g_nAdValue=ADCL;
g_nAdValue|=(uint)(ADCH<<8);
int main(void)
//内部2.56V 参考电压,0 通道
ADMUX=0Xc0;
//使能ADC,中断允许,自由模式, 时钟:ck/8
ADCSRA=_BV(ADEN)|_BV(ADIE)|_BV(ADFR)|_BV(ADPS1)|_BV(ADPS0);
IoInit();//标准输入/输出初始化
ADCSRA|=_BV(ADSC);//自由模式开始转换
for(i=0;i<100;i++)
_delay_loop_2(4 * 250 * 10);//10ms
printf("%d\n",g_nAdValue);
以上是ATMega8 A/D 转换程序的两种方式,在第一种查询方式中ADC 按单次转换模式工作,每次转换均由置位ADSC 触发。在中断方式示例中ADC 按自由模式工作,自第一次置位ADSC 起ADC 就连续不断的进行采样转换、进行数据更新。
第四章 使用C 语言标准I/O 流调试程序
4.1 avr-libc标准I/O流描述
avr-libc 提供标准I/O流stdin, stdout和stderr。但受硬件资源的限制仅支持标准C语言I/O流的部分功能。由于没有操作系统支持,avr-libc又不知道标准流使用的设备,在应用程序的startup过程中I/O流无法初始化。同样在avr-libc中没有文件的概念,它也不支持fopen()。做为替代 fdevopen()提供流与设备间的连接。fdevopen需要提供字符发送、字符接收两个函数,在avr-libc中这两个函数对于字符流与二进制流是没有区别的。
三个核心函数
fdevopen()
应用程序通过fdevopen函数为流指定实际的输入输出设备。
FILE* fdevopen( int(* put) (char), int(* get)(void), int opts __attribute__((unused))
前两个参数均为指向函数的指针,它们指向的函数分别负责向设备输出一字节和从设备输入一字节的函数。第三个参数保留,通常指定0。
如果只指定put指针,流按写方式打开,stdout 或 stderr成为流的引用名。
如果只指定get指针,流按只读方式打开,stdin成为流的引用名。
如果在调用时两者都提供则按读写方式打开,此时stdout、stderr和stdin相同,均可做为当前流的引用名。
(1)向设备写字符函数:
int put(char c)
返回0表示字符传送成功,返回非零表示失败。
另外,字符&#39;\n&#39;被I/O流函数传送时直接传送一个换行字符,因此如果设备在换行前需要回车,应当在put函数里发送 &#39;\n&#39;前发字符&#39;\r&#39;。
以下是一个基于UART的 put 示例:
int uart_putchar(char c)
if(c==&#39;\n&#39;)
uart_putchar(&#39;\r&#39;);
loop_until_bit_is_set(UCSRA,UDRE);
(2)从设备输入字符函数
int get(void)
get 函数从设备读取一字节并按 int 类型返回,如果读取时发生了错误需返回 &1。
vfprintf()
int vfprintf ( FILE * __stream, const char * __fmt, va_list __ap )
vfprintf 将__ap列出的值按__fmt 格式输出到流__stream。返回输出字节数,若产生错误返回EOF。
vfprintf是libc提供的I/O流格式化输出函数的基础,为避免应用中用不到的功能占用宝贵的硬件资源,vfprintf函数支持三种不同链接模式。
(1) 在默认情况下它包含除格式转换外的所有功能
(2) 最小模式仅包含基本整数类型和字符串转换功能要用最小模式链接此函数,使用的链接选项如下:
-Wl,-u,vfprintf -lprintf_min
(3) 完全模式支持浮点数格式转换在内的所有功能。
完全模式链接选项如下:
-Wl,-u,vfprintf -lprintf_flt &lm
int vfscanf ( FILE * __stream, const char * __fmt, va_list __ap )
vfscanf 是libc 提供的I/O 流格式化输入函数的基础, 它从__stream 流按字符格式读取__fmt 内容后按转换规则将数据保存到__ap 内。与vfprintf 类似vfscanf 也支持三种不同链接模式。
(1)在默认情况下它支持除浮点数格式和格式&%[&外的所有转换。
(2)最小模式链接选项:
-Wl,-u,vfscanf -lscanf_min -lm
(3)完全模式链接选项:
-Wl,-u,vfscanf -lscanf_flt &lm
4.2 利用标准I/0流调试程序
在程序的调试阶段,提供数据格式化输入/输出功能的标准I/O函数是个非常有用的工具,而单片机UART接口是标准I/O的比较合适设备
图4-1 UART 实现I/O 流电路原理图
avr-libc 标准 i/o 测试程序
mcu:atmega8
char g_aString[81];
//uart 发送一字节
int usart_putchar(char c)
if(c==&#39;\n&#39;)
usart_putchar(&#39;\r&#39;);
loop_until_bit_is_set(UCSRA,UDRE);
//uart 接收一字节
int usart_getchar(void)
loop_until_bit_is_set(UCSRA,RXC);
return UDR;
void IoInit(void)
//uart 初始化
UCSRB=_BV(RXEN)|_BV(TXEN);/*(1<<RXCIE)|(1<
UBRRL=25; //9600 baud 6MHz:38 4MHz:25
//流与设备连接
fdevopen(usart_putchar,usart_getchar,0);
int main(void)
vfprintf(stdout,"测试1[输入数字]:\n",0);
vfscanf(stdin,"%d",&tmp);
vfprintf(stdout,"您的输入为:%d\n",tmp);
printf("测试2[输入一字符串]:\n");
scanf("%s",g_aString);
printf("您的输入为:%s\n",g_aString);
printf_P(PSTR("测试3[输入数字]:\n"));
scanf_P(PSTR("%d"),&tmp);
printf_P(PSTR("您的输入为:%d\n"),tmp);
三.监测工具
图4-2 PrintMonitor 运行界面
监测工具PrintMonitor 运行界面如图4-2 所示,它属于windows 应用程序,由VisualBasic6.0 编写,请到 下载源代码。
第五章 AT89S52 下载器的制作
5.1 LuckyProg S52 概述
ATMEL 推出的89S 系列单片机具有类似AVR 的ISP 编程功能,单片机ISP 接口为用户提供了一种串行编程方法。ISP 功能就象操作串行EEPROM 存储器那样使单片机的编程变得简单方便。
本章将介绍一种用AVR(AT90S2313)实现的AT89S52 单片机ISP 编程器:LuckyProg S52。
LuckyProg S52 工作原理:
如图5-1 所示,编程单片机AT90S2313 与计算机用RS232 通信,2312 从串行口获得编程命令和数据后用ISP 程序下载接口对AT89S52 编程。
图5-1 LuckyProg S52 功能:
图5-2 为LuckyProg S52 的电中原理图,图中AT90S2313 的UART 口与计算机RS232标准串行接口之间的电平转换被省略,可参考图4-1。
用ISP 口下载程序时AT89S52 必需有时钟源,为此在XTAL0 与XTAL1 间接一个6MHz晶振,ISP 数据通信口MOSI、MISO 和SCK 均接在AT90S2313 的普通I/O 口上,而RST脚由I/O 口通过一个控制。
图5-2 LuckyProg S52 ISP 下载电路原理图
5.2 AT89S52 ISP 功能简介
串行数据的输入与输出时序
数据在SCK 的上升沿输入到52,SCK 的下降沿输出。另外必须保证串行时钟SCK 的周期至少大于是6 个CPU 时钟(XTAL1 上的)周期。
串行编程算法
1. 上电过程
在VCC 和GND 间加上的同时RST 脚加高电平。至少等待时10ms。
2.发送串行编程使能命令
如果通信失步则串行编程失败。如果同步则在编程时钟的第四个字节器件响应0X69,表示编程使能命令成功。不论响应正确与否,必需保证四字节的时钟周期。
通过写指令可对程序存储器的每一字节进行编程。一个写指令使单片机进入自定时的编程模式,在5V 编程电压下典型编程时间少于1ms。
任意位置的程序数据可通过读指令从引脚步MISO/P1.6 读出,实现定写入数据的校验。
5.编程操作结束后将RST 引脚拉低,使器件进入正常工作模式。
表 5-1 AT89S52 ISP 下载命令
注:1.锁定位与模式对应
模式1(B1=0、B2=0):无锁定保护
模式2(B1=0、B2=1):内部锁定位1 有效
模式3(B1=1、B2=0):内部锁定位2 有效
模式4(B1=1、B2=1):内部锁定位3 有效
1.在模式3 和4 下不能读厂标
2.将Reset 拉高后SCK 至少保持64 个时钟周期才可执行编程允许命令,在页读写中命令和地址后数据由0到255 的顺序传送,只有接收完这256 字节的数据后下一个指令才能就绪。
5.3 程序设计
延时功能函数
通常在单片机C 程序里的延时模块为一个计数循环,延时时间的长短往往是先估计,后通过实验或仿真等方法来验证。avr-libc 提供了两个延时API 函数,利用这两个函数我们可以较精确的产生所需的延时函数。
第一个函数声明如下:
void _delay_loop_1(unsigned char count);
它的延时时间为count & 3 个系统时钟周期,计数器count 为8 位无符号整数,第二个函数声明格式为:
void _delay_loop_2(unsigned int count);
它将延时count & 4 个系统时钟周期,计数器count 为16 位无符号整数。
若要调用这两个函数,需先包函头文件 delay.h,实际上这两个函数的实现就在此文件里,我们可以从WINAVR安装目录里的\AVR\INCLUDE\AVR子目录里找到并查看它的内容,以下为_delay_loop_2 的源程序:
static inline void
_delay_loop_2(unsigned int __count)
asm volatile (
"1: sbiw %0,1" "\n\t"
: "=w" (__count)
: "0" (__count)
首先要说明的是,由于函数的实现写在了头文件里,所以被多个源文件包含是可能的,为此有必要将它声明成局部函数(static)。函数内容为内嵌汇编方式,有关内嵌汇编看第8章,这里我们只需知道它的执行需要count * 4 个时钟周期。要注意的是,inline 关键字说明了函数是内连函数,内连函数如同汇编程序里的宏,编译结果是在每一个调用的地方插入一次函数的内容。为此有程序空间要求且调用频率高的应用中再写一个延时函数是有必要的。
以下是为编程器主控单片机AT90S2313 写的延时程序,它以毫秒为单位执行延时任务。
void DelayMs(unsigned int t)
for(i=0;i<T;I++)
_delay_loop_2(FEQ * 250 - 1);
其中FEQ 为系统振荡频率(以M 为单位)。
AT90S2313 程序清单
#define uchar unsigned char
#define uint unsigned int
#define SETLED PORTB&=0xF7
#define CLRLED PORTB|=0X80
#define FREQ 6 //时钟 6MHz
#define MOSI 4
#define MISO 6
#define SCK 0
#define RST 1
#define ACK 0xaa
#define ERR 0XBB
#define CMOD 0XCC;
uchar g_bTxdPos=0; //UART 发送定位数
uchar g_bTxdLen=0; //发送长度设置绶冲
uchar g_bRxdPos=0; //UART 接收定位数
uchar g_bRxdLen=0; //接收长度设置绶冲
uchar g_aMemBuf[32]; //数据绶冲
void DelayMs(uint t)
for(i=0;i<T;I++)
_delay_loop_2(250*FREQ-1);//delay 1ms
wdt_reset();
void DelayBus(void)
_delay_loop_1(4);
wdt_reset();
void ISP_WriteByte(uchar dat)
for(i=0;i<8;i++)
if(dat&0x80)
sbi(PORTB,MOSI);
cbi(PORTB,MOSI);
sbi(PORTB,SCK);
DelayBus();
cbi(PORTB,SCK);
DelayBus();
uchar ISP_ReadByte(void)
uchar ret=0;
for(i=0;i<8;i++)
sbi(PORTB,SCK);
DelayBus();
if(PIND&0x40)
cbi(PORTB,SCK);
DelayBus();
///////////////////////串口处理/////////////////////////////
//接收中断
SIGNAL(SIG_UART_RECV)
uchar c=UDR;
if(g_bRxdLen>0)
g_aMemBuf[g_bRxdPos++]=c;
g_bRxdLen--;
//发送中断
SIGNAL (SIG_UART_TRANS)
if(--g_bTxdLen>0)
UDR=g_aMemBuf[++g_bTxdPos];
//等待接收完成
void WaitRecv(void)
while(g_bRxdLen>0)
DelayBus();
//发送指定字节
void SendToUart(uchar size)
g_bTxdPos=0;
g_bTxdLen=
UDR=g_aMemBuf[0];
while(g_bTxdLen>0)
DelayBus();
//接收指定字节
void RecvFromUart(uchar size,uchar bwait)
g_bRxdPos=0;
g_bRxdLen=
WaitRecv();
//////////////////////////////////////////////////////////
//S52 编程允许
uchar PrgEn(void)
//MOSI、SCK 设为输出
cbi(PORTB,SCK);
cbi(PORTB,MOSI);
sbi(DDRB,MOSI);
sbi(DDRB,SCK);
cbi(PORTB,RST);
DelayMs(100);
ISP_WriteByte(0xac);
ISP_WriteByte(0x53);
ISP_WriteByte(0);
if(ISP_ReadByte()==0x69)
//S52 复位
void PrgDs(void)
//MOSI、SCK 设为输入高阻
cbi(PORTB,MOSI);
cbi(PORTB,SCK);
cbi(DDRB,MOSI);
cbi(DDRB,SCK);
cbi(PORTB,RST);
DelayMs(500);
sbi(PORTB,RST);
void ReadDevice(void)// CMD : 1
uchar i,j,k;
uchar pageaddress=g_aMemBuf[1];
uchar pagecount=g_aMemBuf[2];
if(!PrgEn())
g_aMemBuf[0]=ERR;
g_aMemBuf[0]=ACK;
SendToUart(1);
for(k=0;k<PAGECOUNT;K++)
ISP_WriteByte(0x30);
ISP_WriteByte(pageaddress++);//Write address
for(i=0;i<8;i++)
for(j=0;j<32;j++)
g_aMemBuf[j]=ISP_ReadByte();
SendToUart(32);
g_aMemBuf[0]=ACK;
void WriteDevice(void)//CMD : 3
uchar i,j,k;
if(PrgEn()==0)
g_aMemBuf[0]=ERR;
uchar pageaddress=g_aMemBuf[1];
uchar pagecount=g_aMemBuf[2];
for(k=0;k<PAGECOUNT;K++)
ISP_WriteByte(0x50);
ISP_WriteByte(pageaddress++); //Write address
for(i=0;i<8;i++)
g_aMemBuf[0]=3;
SendToUart(1);
RecvFromUart(32,1);
for(j=0;j<32;j++)
ISP_WriteByte(g_aMemBuf[j]);
DelayMs(1);
//DelayMs(256);
g_aMemBuf[0]=ACK;
void EraseDevice(void) // CMD : 2
if(PrgEn()==0)
g_aMemBuf[0]=ERR;
ISP_WriteByte(0xac);
ISP_WriteByte(0x80);
ISP_WriteByte(0x0);
ISP_WriteByte(0x0);
DelayMs(1000);
g_aMemBuf[0]=ACK;
//写锁定位
void WriteLockBits(void) // CMD: 4
if(PrgEn()==0)
g_aMemBuf[0]=ERR;
temp=0xe0;
if(g_aMemBuf[1])
temp|=0x02;
if(g_aMemBuf[2])
temp|=0x1;
ISP_WriteByte(0xac);
ISP_WriteByte(temp);
ISP_WriteByte(0);
ISP_WriteByte(0);
g_aMemBuf[0]=ACK;
//读锁定位
void ReadLockBits(void)//CMD :5
if(PrgEn()==0)
g_aMemBuf[0]=ERR;
ISP_WriteByte(0x24);
ISP_WriteByte(0);
ISP_WriteByte(0);
g_aMemBuf[0]=ISP_ReadByte();
g_aMemBuf[0]>>=2;
g_aMemBuf[0]&=7;
//////////////////////////////////
//入口////////////////////////////
int main( void )
DelayMs(1000);
//i/o 口初始化
PORTB=0X08;
DDRB=0X09;
PORTD=0XFF;//上拉开
//uart 初始化
UCR=(1<<RXCIE)|(1<<TXCIE)|(1<<RXEN)|(1<<TXEN);
UBRR=38; //UBRR=FCK/(9600*16) -1
wdt_enable(WDTO_1S);
//复位目标板
//复位延时
DelayMs(1000);
RecvFromUart(3,1);
switch(g_aMemBuf[0])
g_aMemBuf[0]=ACK;
ReadDevice();
EraseDevice();
WriteDevice();
WriteLockBits();
ReadLockBits();
g_aMemBuf[0]=ACK;
SendToUart(1);
}//main loop
在写FLASH 存储器时先从计算机读取程序数据到g_aMemBuf 缓冲区,然后用页模式(查表5-1)写入,由于2313 内部RAM 有限,缓冲区g_aMemBuf 的大小定义为32 字节,为此写一页时必需从计算机读程序数据8 次。
上位机程序
上位机程序界面如图5-3 所示,它是由VisualBasic6.0 编写。可执行文件或VisualBasic 源代码可到
第六章 硬件TWI 端口编程
6.1 TWI 模块概述
ATMega 系列单片机片内集成两线制串行接口模块,Atmel 文档称它为TWI 接口。事实上TWI 与PHILIPS 的I2C 是同一回事,之所以叫它TWI 是因为这样的命名可使Atmel 避免交术语版税。所以,TWI 兼容I2C 更一种说法。
关于I2C 协议参考PHILIPS 相关文档。
AVR 硬件实现的TWI 接口是面向字节和基于中断的,相对软件模拟I2C 总线有更好的实时性和代码效率,引脚输入部分还配有毛刺抑制单元,可去除高频干扰。另外,结合AVR I/0端口功能,在TWI 使能时可设置SCL 和SDA 引脚对应的I/O 口内部有效,这样可省去I2C 要求的外部两个上拉电阻。
下面以MEGA8 为例,简要介绍TWI 接口的工作方式。
在I2C 总线上MEGA8 可扮演主控制器(主控模式)和从器件(被控模式)的角色。
不论主控模式还是被控模式都应当将TWI 控制寄存器TWCR 的TWEN 位置1 从而使能TWI模块。TWEN 位被置位后I/O 引脚PC5 和PC4 被转换成SCL 和SDA,该管脚上的斜率限制和毛刺滤波器生效。如果外部没有接上拉电阻可用类似如下的操作使能该管脚上的内部上拉电阻:
PORTC|=0X30;
对TWI 控制寄存器TWCR 的操作可在总线上产生START 和STOP 信号,从一个START 到STOP 被认为是主控模式的行为。
将TWI 地址寄存器TWAR 的第一位TWGCE 置有效,同时将TWI 控制寄存器TWCR 的TWEA(应答允许)位置1,TWI 模块就可以对总线上对它的寻址做出应答,并置状态字。
对总线的操作或总线上产生事件后应用程序应当根据TWI 状态寄存器值来确定下一步的操作。关于不同模式下的状态值的详细描述参考MEGA8 的数据手册。
对TWI 模块的操作均为寄存器的读写操作,Avr-libc 没有提供专门的API。 文件twi.h定义了状态字的常量和一个返回状态字的宏。
6.2 主控模式操作实时时钟DS1307
一 实时时钟DS1307 介绍
DS1307是、两线制串行读写接口、日历和时钟数据按BCD码存取的时钟/日历芯片。
它提供秒、分、小时、星期、日期、月和年等时钟日历数据。另外它还集成了如下几点功能:
?? 56 字节掉电时电池保持的NV SRAM 数据存储器
?? 的方波信号输出
?? 掉电检测和自动切换电池供电模式
DS1307 把8 个寄存器和56 字节的RAM 进行了统一编址,具体地址和寄器数据组织格式如下表:
表 6-1 DS1307 内存组织结构
在读写过程中DS1307 内部维护一个地址指针,通过写操作可对它负值,读和写每一字节时自动加一,当指针越过DS1307 内部RAM 尾部时指针将返回到0 地址处。
DS1307 的时钟和日历数据按BCD 码存储。
方波信号输出功能:
方波信号输出功能从SQW/OUT 引脚输出设置频率的方波,CONTROL 寄存器用于控制SQW/OUT 脚的输出。
BIT7(OUT):此位表示在方波输出被禁止时SQW/OUT 脚的逻辑电平,在SQWE=0(输出禁止)时若OUT 为1 则SQL/OUT 脚为高电平, 反之亦然。
BIT4(SQWE)方波输出允许/禁止控制位,1 有效。
BIT0(RS0)、BIT1(RS1)用于设定输出波形的频率,如下表:
表6-2 方波信号输出频率设置
RS1 RS0 输出频率(Hz)
要注意的是,00h 地址的第7 位为器件时钟允许位(CH),由于在在开始上电时内部RAM内容随机,所以在初始化时将CH 位设零(时钟允许)是非常重要的。DS1307 在TWI 总线上是个从器件,地址(SLA)固定为1101000。DS1307 写操作& TWI 被控接收模式主控器件按如下顺序将数据写入到DS1307 寄存器或内部RAM 中:
1.START 信号
2.写SLA+W(0xd0)字节,DS1307 应答(ACK)
3.写1 字节内存地址(在以下第四步写入的第一字节将存入到DS1307 内该地址处,DS1307应答)
4.写数据(可写多个字节,每一字节写入后DS1307 内部地址计数器加一,DS1307 应答)
5.STOP 信号
DS1307 读操作& TWI 被控发送模式
主控器件按如下顺序将DS1307 寄存器或内部RAM 数据读取:
1.START 信号
2.写SLA+R(0xd1)字节,DS1307 应答(ACK)
3.读数据(可读多个字节,读取数据的DS1307 内部地址由上次写操作或读操作决定,读取每一字节DS1307 内部地址计数器加一,主器件应答,读取最后一字节时主器件回应一NACK信号)
4.STOP 信号
二 DS1307 实验电路
DS1307 与Mega8 间的连接如6-1 所示,DS1307 的X1 和X2 管脚需接32768Hz 晶振。
Vbat 引脚接的电池电压必需在2.0V~3.5V 范围内。当VCC 引脚上的电压降到1.25 倍电池电压时DS1307 内部写保护电路生效,RTC 数据和内部RAM 的读写补禁止。
图6-1 Mega8 与DS1307 的连接电路
三 程序设计
以下是操作DS1307 的示例程序,利用基于USART 的标准I/O 实现读写日历和时钟。
Mega8 硬件TWI 接口读写实时时钟DS1307 程序
文件名:main.c
----------
#define uint unsigned int
#define uchar unsigned char
#define FREQ 4
#define DS1307_ADDR 0XD0
#define TW_ACK 1
#define TW_NACK 0
#define RTC_READ 1
#define RTC_WRITE 0
FILE *g_hF
uchar g_aTimeBuf[7];//日历/时钟BCD 格式缓冲区
uchar g_aTimeBin[7];//时钟/日历二进制格式缓冲区
void DelayMs(uint ms)
for(i=0;i<MS;I++)
_delay_loop_2(FREQ *250);
/*******************标准I/O 功能****************开始********/
//标准I/O 输出函数
int usart_putchar(char c)
if(c==&#39;\n&#39;)
usart_putchar(&#39;\r&#39;);
loop_until_bit_is_set(UCSRA,UDRE);
//标准I/O 输入函数
int usart_getchar(void)
loop_until_bit_is_set(UCSRA,RXC);
return UDR;
void IoInit(void)
//串行口初始化
UCSRB=_BV(RXEN)|_BV(TXEN);/*(1<<RXCIE)|(1<
UBRRL=25; //9600 baud 6MHz:38 4MHz:25
//UART 用于标准I/O 输入输出
g_hFile=fdevopen(usart_putchar,usart_getchar,0);
/*******************标准I/O 功能**************结束**********/
/*************主模式TWI 操作部分*************开始**************/
//总线上起动停止条件
void twi_stop(void)
TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN);
//总线上起动开始条件
uchar twi_start(void)
TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN);
while ((TWCR & _BV(TWINT)) == 0) ;
return TW_STATUS;
//写一字节
uchar twi_writebyte(uchar c)
TWCR = _BV(TWINT) | _BV(TWEN);
while ((TWCR & _BV(TWINT)) == 0);
return TW_STATUS;
//读一字节 ack: true 时发ACK,false 时发NACK
uchar twi_readbyte(uchar *c ,uchar ack)
uchar tmp=_BV(TWINT)|_BV(TWEN);
tmp|=_BV(TWEA);
while ((TWCR & _BV(TWINT)) == 0) ;
return TW_STATUS;
/*************主模式TWI 操作部分*************结束**************/
/*************DS1307 操作**************开始**************/
//对DS1307 内存连续的写操作
uchar rtc_write(uchar addr,uchar *buf,uchar len)
twi_start();
twi_writebyte(DS1307_ADDR|TW_WRITE);
twi_writebyte(addr);//write address
for(i=0;i<LEN;I++)
twi_writebyte(buf[i]);
twi_stop();
//对DS1307 内存连续的读操作
uchar rtc_read(uchar addr,uchar *buf,uchar len)
rtc_write(addr,0,0);//set address
DelayMs(10);
twi_start();
twi_writebyte(DS1307_ADDR|TW_READ);
for(i=0;i<LEN-1;I++)
twi_readbyte(buf+i,TW_ACK);
twi_readbyte(buf+i,TW_NACK);
twi_stop();
/*************DS1307 操作**************结束**************/
/*************接口部分****************开始**************/
//初始化TWI 功能
void RtcInit(void)
//更新或读取DS1307 日历/时间数据
uchar RtcUpdateData(uchar direction)
if(direction) //读
ret=rtc_read(0,g_aTimeBuf,7);
ret=rtc_write(0,g_aTimeBuf,7);
//读DS1307 用户RAM
uchar RtcReadRAM(uchar addr,uchar *buf,uchar len)
return rtc_read(addr,buf,len);
//写DS1307 用户RAM
uchar RtcWriteRAM(uchar addr,uchar *buf,uchar len)
return rtc_write(addr,buf,len);
uchar byte_bintobcd(uchar bin)
bin&=0x7f;
ret=bin/10;
ret|=bin%10;
uchar byte_bcdtobin(uchar bcd)
ret=bcd & 0x0f;
ret+=(bcd>>4)*10;
//将二进制格式缓冲区(g_aTimeBin)内容转换成BCD 格式后保存到
//BCD 格式缓冲区(g_aTimeBuf)
void RtcBinToBCD()
g_aTimeBin[0]&=0x7f;
g_aTimeBin[1]&=0x7f;
g_aTimeBin[2]&=0x3f;
g_aTimeBin[3]&=0x07;
g_aTimeBin[4]&=0x3f;
g_aTimeBin[5]&=0x1f;
g_aTimeBin[6]&=0
for(i=0;i<7;i++)
g_aTimeBuf[i]=byte_bintobcd(g_aTimeBin[i]);
//将BCD 格式缓冲区(g_aTimeBuf)内容转换成二进制格式后保存到
//二进制格式缓冲区(g_aTimeBin)
void RtcBCDToBin()
for(i=0;i<7;i++)
g_aTimeBin[i]=byte_bcdtobin(g_aTimeBuf[i]);
//写DS1307 配置字节
void RtcSetSQWOutput(uchar en,uchar level)
//en:方波输出允许 TRUE 有效 level:如果输出禁止 OUT 口的逻辑电平
uchar c=0;
if(en) //enable
else //disable
rtc_write(7,&c,1);
/*************接口部分***************结束**************/
int main(void)
int tmp[7];//从标准I/O 读取缓冲区
RtcInit();
printf_P(PSTR("输入命令:g - 打印日历/时钟,s - 设置日历/时钟,\
h - 帮助信息\n"));
while(1) //main loop
scanf("%c",&c);
if(c==&#39;g&#39;)
RtcUpdateData(RTC_READ);
RtcBCDToBin();
printf_P(PSTR("当前日历/时钟:%d 年%d 月%d 日 星期\
%d %d:%d:%d\n"),\
g_aTimeBin[6],g_aTimeBin[5],g_aTimeBin[4],g_aTimeBin[3],\
g_aTimeBin[2],g_aTimeBin[1],g_aTimeBin[0]);
else if(c==&#39;s&#39;)
printf_P(PSTR("请按
格式输入:\n"));
scanf("%d,%d,%d,%d,%d,%d,%d",\
tmp+6,tmp+5,tmp+4,tmp+3,tmp+2,tmp+1,tmp);
for(i=0;i<7;i++)
g_aTimeBin[i]=(uchar)tmp[i];
RtcBinToBCD();
RtcUpdateData(RTC_WRITE);
printf_P(PSTR("\n 设置完成!\n"));
else if(c==&#39;h&#39;)
printf_P(PSTR("输入命令:g - 打印日历/时钟,s - 设置日历/时钟,\
h - 帮助信息\n"));
}//main loop
将编译后的代码写入Mega8, 按第四章所示电路连接到计算机就可以用PrintMonitor测试程序了,测试结果如图6-2 所示。
图 6-2 PrintMonitor 测试结果
twi_start,twi_writebyte,twi_readbyte 三个函数操作后均返回TWI 状态寄存器值,在实际应用中应当检测这样的值,处理出现错误的情况。
6.3 两个Mega8 间的TWI 通信
两个Mega8 用TWI 总线通信,主要为了说明Mega8 在TWI 从模式下工作的编程方法。
一. 测试电路
两片Mega8 间的连接如图 6-3 所示。
图6-3 电路两个MEGA8 间的电路连接图
Master Mega8 的PD2 引脚接有一个按键,当键被按下时起动一次的TWI 写操作,Slave Mega8 的RXD、TXD 引脚通过电平转换后接到PC 机串行口,用PrintMonitor 监测其打印输出的信息。
二. 程序设计
1.主模式单片机程序
主模式程序用查询方式检测并等待按键,当键被按下时向TWI 口写0~9 。
文件名:master.c
两个Mega8 间的TWI 通信实验主模式单片机程序
内部4MHz 振荡器
----------
#define uint unsigned int
#define uchar unsigned char
#define WAITPRINTDEBUG DelayMs(100) //为从模式单片机打印调试信息而延时
#define KEY 0X04
#define FREQ 4
#define TWI_ADDRESS 0X32
void DelayMs(uint ms)
for(i=0;i<MS;I++)
_delay_loop_2(FREQ *250);
/*************主模式TWI 操作部分*************开始**************/
//总线上起动停止条件
void twi_stop(void)
TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN);
//总线上起动开始条件
void twi_start(void)
uchar trycount=0;
TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN);
while ((TWCR & _BV(TWINT)) == 0) ;
return TW_STATUS;
//写一字节
void twi_writebyte(uchar c)
TWCR = _BV(TWINT) | _BV(TWEN);
while ((TWCR & _BV(TWINT)) == 0);
return TW_STATUS;
//读一字节 ack: true 时发ACK,false 时发NACK
uchar twi_readbyte(uchar *c ,uchar ack)
uchar tmp=_BV(TWINT)|_BV(TWEN);
tmp|=_BV(TWEA);
while ((TWCR & _BV(TWINT)) == 0) ;
return TW_STATUS;
/*************主模式IIC 操作部分*************结束**************/
//检测按键
uchar WaitKeyDown(void)
key=PIND & KEY;
if( key!=KEY)
DelayMs(30);
key=PIND & KEY;
if(key!=KEY)
DelayMs(1);
while((PIND & KEY)!=KEY)
DelayMs(10);
int main(void)
//便能SCL、SDA 引脚内部上拉电阻
PORTC=0X30;
TWBR=73;//波特率
WaitKeyDown();
twi_start();
WAITPRINTDEBUG;
twi_writebyte(TWI_ADDRESS|TW_WRITE);
WAITPRINTDEBUG;
for(i=0;i<10;i++)
twi_writebyte(i);
WAITPRINTDEBUG;
twi_stop();
2.从模式单片机程序
从模式程序用查询方式等待TWI 端口中断标志,在TWI 通信的不同阶段从UART 打印出
对应测试信息。
文件名:slave.c
两个Mega8 间的TWI 通信实验从模式单片机程序
外部4MHz 晶振
#define uint unsigned int
#define uchar unsigned char
#define TWI_ADDRESS 0X32
//标准I/O 输出函数
int usart_putchar(char c)
if(c==&#39;\n&#39;)
usart_putchar(&#39;\r&#39;);
loop_until_bit_is_set(UCSRA,UDRE);
void IoInit(void)
//使能SCL、SDA 引脚内部上拉电阻
PORTC=0X30;
//串行口初始化
UCSRB=_BV(RXEN)|_BV(TXEN);/*(1<<RXCIE)|(1<
UBRRL=25; //9600 baud 6MHz:38 4MHz:25
//UART 用于标准I/O 输入输出
fdevopen(usart_putchar,0,0);
//TWI 接口初始化,从器件模式
TWAR=TWI_ADDRESS | _BV(TWGCE);
TWCR=_BV(TWEA) | _BV(TWEN);
int main(void)
uchar i,j=0;
while ((TWCR & _BV(TWINT)) == 0);
i=TW_STATUS;
case TW_SR_SLA_ACK:
printf("START\nSLA+W\n");
case TW_SR_DATA_ACK:
printf("收到:%d",TWDR);
printf(" %d",TWDR);
case TW_SR_STOP:
printf(";\nSTOP\n\n");
printf("error:%x",(int)i);
TWCR=_BV(TWEA) | _BV(TWEN)|_BV(TWINT); //清除TWINT 位
三. 测试结果
从模式程序输出结果如图6-4 所示,当主模式单片的键被按下后从单片机先检测到START 条件,然后收到本机SLA+W,再接收1~9 的数据,最后检测到STOP 条件。
图6-4 从模式程序打印输出结果
第七章 BootLoader 功能应用
7.1 BootLoader 功能介绍
BootLoader 提供我们通常所说的IAP(In Applicaion Program)功能。多数Mega 系列单片机具有片内引导程序自编程功能(BootLoader)。mcu 通过运行一个常驻FLASH 的BootLoader 程序,利用任何可用的数据接口读取代码后写入自身FLASH存储器中 ,实现自编程目的。
下面用ATMEGA8 来说明此功能
ATMEGA8 片内具有8K 的FLASH 程序存储器,BootLoader 功能将其分为 应用程序区(Application section)和引导加载区(Boot loader section), 通过设置熔丝位BOOTSZ0 和BOOTSZ1 可将应用区和引导区的大小分别设置成6K/2K 、7K/1K 、7K512B/512B 和7K768B/256B。详细请参考ATMEGA8 数据手册。
另外,ATMEGA8 还有一个熔丝位BOOTRST 用于设置复位向量,当BOOTRST 未被编程时器件的复位从应用程序区首地址(既0)开始执行,当BOOTRST 被编程时器件的复位从引导区首地址开始执行。
使用BootLoader ,首先应该根据BootLoader 程序的大小设置好BOOTSZ0 和BOOTSZ1熔丝位,然后编程BOOTRST 熔丝位使单片机的复位从引导区开始执行,之后要把BootLoader 程序定位并写入到引导区(首地址取决于熔丝位ROOTSZ0 和BOOTSZ1 的编程状态)。以上过程需要使用ISP 或并行编程方式来实现。
当单片机上电复位后BootLoader 程序开始执行,它通过USART、TWI 或其它方式从计算机或其它数据源读应用区代码并写入到应用区。事实上 BootLoader 程序有能力读写整个FLASH 存储器,包括BootLoader 程序所在的引导区本身,只是写自身所在的引导区的应用较少见。
7.2 avr-libc 对BootLoader 的支持
avr-libc 提供一组C 程序接口API 来支持BootLoader 功能。
在boot.h 中这些API 均为预定义宏,以下为其主要几个宏。
boot_page_erase ( address )
擦除FLASH 指定页
其中address 是以字节为单位的FLASH 地址
boot_page_fill ( address, data )
填充BootLoader 缓冲页,address 为以字节为单位的缓冲页地址(对mega8 :0~64),而data 是长度为两个字节的字数据,因此调用前address 的增量应为2。 此时data 的高字节写入到高地址,低字节写入到低地址。
boot_page_write ( address )
boot_page_write 执行一次的SPM 指令,将缓冲页数据写入到FLASH 指定页。
boot_rww_enable ( )
RWW 区读使能
根据自编程的同时是否允许读FLASH 存储器,FLASH 存储器可分为两种类型:
可同时读写区( RWW Read-While-Write ) 和 非同时读写区( NRWW Not Read-While-Write)。对于MEGA8 RWW 为前6K 字节 NRWW 为后2K 字节。
引导加载程序对RWW 区编程时mcu 仍可以从NRWW 区读取指令并执行,而对NRWW 区编程时mcu 处于挂起暂停状态。
在对RWW 区自编程(页写入或页擦除)时,由硬件锁定RWW 区 , RWW 区的读操作被禁止,在对RWW 区的编程结束后应当调用boot_rww_enable() 使RWW 区开放。
7.3 BootLoader 应用实例
本节介绍一种BootLoader 的一个简单应用实例 - LuckyProg M8BL。 BootLoader 程序通过UART 与计算机通信,执行读、写和执行FLASH 应用区的操作。
一.硬件:
如图7-1 ,MEGA8 的UART 端口通过MAX232 的电平转换后与计算机RS-232 串行通信口连接。
图7-1 LuckyProg M8BL 测试电路
MEGA8 熔丝位设置图如图7-2 所示,:引导加载区分配2048 个字节,BOOTRST 被编程(复位后从引导区开始执行),时钟源选择了外部晶振。
图7-2 Mega8 熔丝位设置
二.引导加载程序
引导加载程序不使用中断,查询方式读写UART,响应UART 传来的读当前页、写当前页、设置当前页地址、运行应用区代码和BootLoader 程序检测等命令。每读或写一页当前页地址增一。
&运行应用区代码&命令将mcu 引导到应用程序区的首地址,在没有特殊情况下只能用硬件复位方式将mcu 重新引导到引导加载区后才能执行下一次的程序加载操作。
引导加载程序清单:
LuckyProg M8BL 引导加载程序V1.0
文 件 名 : mboot.c
器 件:ATMEGA8
时 钟:4MHz
作 者:芯 艺
编 译:WinAVR-
#define uchar unsigned char
#define uint unsigned int
#define FREQ 4
#define UART_ACK 0XAA
#define PAGE_SIZE 64 //按字节
uint g_wPageIndex=0;
uchar g_aPageTemp[PAGE_SIZE];
void (*reset)(void)=0x0000;
void uart_putc(uchar c)
while( !(UCSRA & (1<
uchar uart_getc(void)
while( !(UCSRA & (1<
return UDR;
void WritePage(void)
// 接收当前页面数据
for(i=0;i<PAGE_SIZE;I++)
g_aPageTemp[i]=uart_getc();
// 页擦除操作
boot_page_erase(g_wPageIndex<<6);
while(boot_rww_busy())
boot_rww_enable();
// 填充缓冲页
for(i = 0; i < PAGE_SIZE; i += 2)
boot_page_fill((unsigned long)i,*((uint *)(g_aPageTemp +i)));
// 页写入操作
boot_page_write(g_wPageIndex<<6);
while(boot_rww_busy())
boot_rww_enable();
g_wPageIndex++;
void ReadPage(void)
for(i=0;i<PAGE_SIZE;I++)
uart_putc(pgm_read_byte(i+(g_wPageIndex*PAGE_SIZE)));
g_wPageIndex++;
int main(void)
//uart 初始化
UBRRL=25;//9600 baud 6MHz:38 4MHz:25
UCSRB=(1<<RXEN)|(1<<TXEN);
while(1) //main loop
tmp=uart_getc();//recv command
switch(tmp)
case 0xB0://设置页地址
g_wPageIndex=uart_getc();
uart_putc(g_wPageIndex);
case 0xBF://运行用户程序
case 0xAF://写一页
WritePage();
uart_putc(UART_ACK);//应答
case 0xA0://读一页
ReadPage();
case UART_ACK://回应检测命令
uart_putc(UART_ACK);
} //switch
} //main loop
编译和链接:
用Mfile 生成一个makefile ,设置好以下变量
mcu = atmega8
TARGET = mboot
之后要做一个额外的修改,那就是将程序入口定位到引导区首地址,这是通过指定 .text 段链接地址来实现的。
在makefile 中找到如下代码
# Linker flags.
# -Wl,...: tell GCC to pass this to linker.
# -Map: create map f}

我要回帖

更多关于 java调用cmd 执行命令 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信