电子百科
1:在相同的系统时钟下AVR运行速度最快;
2: 芯片内部的Flsah、EEPROM、SRAM容量较大;
3:所有型号的Flash、EEPROM都可以反复烧写、全部支持在线编程烧写(ISP);
4:多种频率的内部RC振荡器、上电自动复位、看门狗、启动延时等功能,零外围电路也可以工作;
5:每个IO口都可以以推换驱动的方式输出高、低电平,驱动能力强;
6:内部资源丰富,一般都集成AD、DA模数器;PWM;SPI、USART、TWI、I2C通信口;丰富的中断源等。
目前支持AVR单片机编译器的语言主要有汇编语言、C语言、BASIC语言等。其中C编译器主要有CodeVisionAVR、AVRGCC、IAR、ICCAVR等,C语言编译器由于它具有功能强大、 运用灵活、代码小、运行速度快等先天性的优点,使得它在专业程序设计上具有不可代替的地位。
AVR单片机是1997年由ATMEL公司研发出的增强型内置FLASH的RISC(Reduced Instruction Set CPU) 精简指令集高速8位单片机。AVR的单片机可以广泛应用于计算机外部设备、工业实时控制、仪器仪表、通讯设备、家用电器等各个领域。
AVR的主要特性
高可靠性、功能强、高速度、低功耗和低价位 , 一直是衡量单片机性能的重要指标,也是单片机占领市场、赖以生存的必要条件。
早期单片机主要由于工艺及设计水平不高、功耗高和抗干扰性能差等原因,所以采取稳妥方案:即采用较高的分频系数对时钟分频,使得指令周期长,执行速度慢。以后的 CMOS单片机虽然采用提高时钟频率和缩小分频系数等措施,但这种状态并未被彻底改观(51以及51兼容)。此间虽有某些精简指令集单片机(RISC)问世,但依然沿袭对时钟分频的作法。
AVR单片机的推出,彻底打破这种旧设计格局,废除了机器周期,抛弃复杂指令计算机(CISC)追求指令完备的做法;采用精简指令集,以字作为指令长度单位,将内容丰富的操作数与操作码安排在一字之中(指令集中占大多数的单周期指令都是如此),取指周期短,又可预取指令,实现流水作业,故可高速执行指令。当然这种速度上的升跃,是以高可靠性为其后盾的。
AVR单片机硬件结构采取8位机与16位机的折中策略,即采用局部寄存器存堆(32个寄存器文件)和单体高速输入/输出的方案(即输入捕获寄存器、输出比较匹配寄存器及相应控制逻辑)。提高了指令执行速度(1Mips/MHz),克服了瓶颈现象,增强了功能;同时又减少了对外设管理的开销,相对简化了硬件结构,降低了成本。故AVR单片机在软/硬件开销、速度、性能和成本诸多方面取得了优化平衡,是高性价比的单片机。
AVR单片机内嵌高质量的Flash程序存储器,擦写方便,支持ISP和IAP,便于产品的调试、开发、生产、更新。内嵌长寿命的EEProm可长期保存关键数据,避免断电丢失。片内大容量的RAM不仅能满足一般场合的使用,同时也更有效的支持使用高级语言开发系统程序,并可像MCS-51单片机那样扩展外部 RAM。
AVR单片机的I/O线全部带可设置的上拉电阻、可单独设定为输入/输出、可设定(初始)高阻输入、驱动能力强(可省去功率驱动器件)等特性,使的得I/O口资源灵活、功能强大、可充分利用。
AVR单片机片内具备多种独立的时钟分频器,分别供URAT、I2C、SPI使用。其中与8/16位定时器配合的具有多达10 位的预分频器,可通过软件设定分频系数提供多种档次的定时时间。AVR单片机独有的“以定时器/计数器(单)双向计数形成三角波,再与输出比较匹配寄存器配合,生成占空比可变、频率可变、相位可变方波的设计方法(即脉宽调制输出PWM)”更是令人耳目一新。
增强性的高速同/异步串口,具有硬件产生校验码、硬件检测和校验侦错、两级接收缓冲、波特率自动调整定位(接收时)、屏蔽数据帧等功能,提高了通信的可靠性,方便程序编写,更便于组成分布式网络和实现多机通信系统的复杂应用,串口功能大大超过MCS-51/96单片机的串口,加之AVR单片机高速,中断服务时间短,故可实现高波特率通讯。
面向字节的高速硬件串行接口TWI、SPI。TWI与I2C接口兼容,具备ACK信号硬件发送与识别、地址识别、总线仲裁等功能,能实现主/从机的收/发全部4种组合的多机通信。SPI支持主/从机等4种组合的多机通信。
AVR单片机有自动上电复位电路、独立的看门狗电路、低电压检测电路BOD,多个复位源(自动上下电复位、外部复位、看门狗复位、BOD复位),可设置的启动后延时运行程序,增强了嵌入式系统的可靠性。
AVR单片机具有多种省电休眠模式,且可宽电压运行(5-2.7V),抗干扰能力强,可降低一般8位机中的软件抗干扰设计工作量和硬件的使用量。 AVR单片机技术体现了单片机集多种器件(包括FLASH程序存储器、看门狗、EEPROM、同/异步串行口、TWI、SPI、A/D模数转换器、定时器/计数器等)和多种功能(增强可靠性的复位系统、降低功耗抗干扰的休眠模式、品种多门类全的中断系统、具输入捕获和比较匹配输出等多样化功能的定时器/计数器、具替换功能的I/O端口…… )于一身,充分体现了单片机技术的从“片自为战”向“片上系统SoC”过渡的发展方向。
综上所述,AVR单片机博采众长,又具独特技术,不愧为8位机中的佼佼者。
AVR系列单片机的选型
AVR单片机系列齐全,可适用于各种不同场合的要求。AVR单片机有3个档次:
低档Tiny系列AVR单片机: 主要有Tiny11/12/13/15/26/28等;
中档AT90S系列AVR 单片机: 主要有AT90S1200/2313/8515/8535等; (正在淘汰或转型到Mega中)
高档ATmega系列AVR单片机: 主要有ATmega8/16/32/64/128( 存储容量为8/16/32/64/128 KB)以及ATmega8515/8535等。
随着技术的发展,嵌入式系统的设计及应用对人们的生活产生了很大的影响,并将逐渐改变人们未来的生活方式,在特定的操作系统上开发应用程序,可以使开发人员忽略掉很多底层硬件细节,使得应用程序调试更方便、易于维护、开发周期缩短并且降低开发成本,因而嵌入式操作系统深得开发人员的青睐。
AVR微处理器是Atmel公司开发的8位嵌入式RISC处理器,它具有高性能、高保密性、低功耗、非易失性等优点,而且程序存储器和数据存储器可独立编址,并具有独立访问的哈佛结构。AVR单片机内核有丰富的指令集,通过32个通用寄存器直接与逻辑运算单元相连接,允许在一个周期内一条单一指令访问两个独立的寄存器,这样的结构使代码的执行效率比传统的复杂指令集微处理器快了将近10倍。
AVRX是由1barello编写的源码公开的嵌入式操作系统,它专门针对AVR系列单片机的RTOS,具有免费和可以修改的特点,它的缺点是由于做为一种专用的操作系统很难移植到其他平台上。
1 AVRX 系统的特点
AVRX做为AVR专用RTOS有如下的特点:
◆ 完全支持占先式、优先级驱动的任务调度算法;
◆ 16个优先级,相同的优先级的任务采用Round robin调度算法轮流执行;
◆ 信号量可以用于信号传递、同步和互斥信号量,支持阻塞和非阻塞语法;
◆ 任务之间可以用消息队列相互传递信息,接收和确认消息可以用阻塞和非阻塞调用;
◆ 在中断子程序中,大部分非阻塞的中断服务程序可以使用;
◆ 支持单个定时器的时间队列管理,任何进程都可以设置一个定时器,并且任何一个任务都可以等待定时器时间到;
◆ 支持单步调式运行着的进程;
◆ 程序空间小,包含所有功能的版本占用1000字节;
◆ 与定时器/计算器有关的一些事务可以用AVRX写成任务级代码。
1.1 任务
AVRX2.6为了支持C语言,保存了所有的32个寄存器,最小的上下文是32个寄存器、SREG和PC,总共35个字节。AvrXInitTask()函数给所有的寄存器初始化为0x00;只有进程上下文保存在任务堆栈中,所有其他的使用(包括内核和中断)保存在内核堆栈。这样降低了第一个中断的上下文切换和进入内核API的SRAM消耗。随后的中断(如果允许中断嵌套)嵌入内核堆栈,API不进行上下文切换。
1.2 信号量
信号量是SRAM指针,它们有三中状态:PEND、WAITING和DONE。当一个进程被一个信号量阻塞时,它处于WAITING状态,多个任务可以排队等候一个信号量。在后一种情况下,信号量可以看作互斥信号量。提供的API函数如下:AvrXSetSemaphore、AvrXIntSetSemaphore、AvrXWaitSemaphore、AvrXtestSemaphore、AvrXIntTestSemaphore和AvrXResetSemaphore。
1.3 定时器
定时器控制块(TCB)长度为4(或6)个字节。它们管理一个16位计数值。定时器队列管理器管理一个分类的定时器队列,每个都调整为所有计数器的和到其延时需要的值。提供的API函数如下:AvrXStartTimer、AvrXTimerHandler、AvrXCancelTimer、AvrXWaitTimer、AvrXTestTimer和AvrXDelay。
1.4 消息队列
消息队列用消息控制块(MCB)做为队列首地址。任何进程、中断处理函数和多个进程都可以等待消息。MCB的长度是2或4个字节。消息可以认为是灵活性更大的信号量。提供的API函数如下:AvrXSendMessage、AvrXIntSendMessage、AvrXRecvMessage、AvrXWaitMessage、AvrXAckMessage、AvrXTestMessage和AvrXWaitMessageAck。
1.5 单步运行支持
通过重新汇编内核AVRX,可以允许和禁止单步运行的支持。单步运行可以通过编译内核库时定义下面的变量:#define SIGNALSTEPSUPPORT。
在能够单步运行以前,进程必须先暂停。有两种方法实现:一是仅仅初始化进程但不使能;二是用目标进程的ID调用AvrXSuspend,一旦目标进程挂起,调试SPI就能使用了,提供的API函数有:AvrXStepNext和AvrXSingleStepNext。
1.6 系统对象
AVRX是围绕系统对象的概念而构建的,系统对象包括一个链接和其后面的0个或者若干个字节的数据信号量。进程对象可以根据运行队列和信号量排队。计数器控制块只能根据计数器队列排队。消息控制块只能在消息队列排队。进程根据嵌入对象的信号量等待这些对象。
进程堆栈中可用的SRAM是限制系统规模的主要因素,每个进程都需要至少10~35字节的空间来存储进程上下文。提供的API函数如下:AvrXSetObjectSamaphore、AvrXIntObjectSamaphore、AvrXResetObjectSamaphore、AvrXWaitObjectSamaphore、AvrXTestObjectSamaphore和AvrXIntTestObjectSamaphore。
1.7 系统堆栈
AVRX需要足够大的堆栈来处理所有可能的中断嵌套,每次进入内核将会把10~35字节压进堆栈(标准上下文和返回地址),中断处理可能压进去更多。AVRX的API会临时压入2个以上的字节。GCC或者汇编代码定义于SRAM的顶部,保证AVRX的堆栈在有效SRAM空间之内是设计者的工作。
2 AVRX系统的应用
2.1 AVRX在不同型号AVR单片机上的移植
下面以ATmega16为例,介绍移植工作。
(1)编译器的选择
由于AVRX的编者是在GNU推出的AVR-GCC编译器下编写的,所以选用AVR-GCC编译器可以大大提高AVRX在不同AVR单片机上的移植特性。
(2)重新编译AVRX内核
为了将应用程序成功编译,需要重新编译AVRX内核,重新编译包括下述步骤。
①重新修改AVRX源码的Makefile文件,需要修改的几处如下:
ABSPATH=…/avrx /*更改AVRX原路径到实际路径下*/
修改 MCU=8535
AAVRMCU=1
GCCMCU=at90s$(MCU)
AVRXMCU=_AT90S$(MCU)_
为 ICCMCU=m16
AAVRMCU=3
GCCMCU=atmega16
AVRXMCU=_AT90Mega16_
②重新修改AVRX源码的serialio.s文件,即根据不同的单片机修改串口部分的寄存器定义。需要增添如下代码:
#if defined(UBRRL)
#define UBRR UBRRL
#endif
#if defined(UBRRH)
sts UBRRH,p1h
#endif
③重新编译内核。具体做法是复制一个“令名提示符”到AVRX目录下,运行“命令提示符”,键入“makegcc”命令后运行就完成了AVRX内核的重新编译,会生成很多的.o文件和avrx.a文件。这些文件在以后的应用程序中会使用。
至此就完成了AVRX在ATmega16单片机上的内核移植,接着就可以编写应用程序了。
2.2 在AVRX上编写应用程序
这时候要用一个新的makefile文件,同时自己的程序可以不和AVRX的内核在一个目录,但是要指出依赖文件的明确路径。makefile的框架可以采用Winavr的sample文件夹下的makefile文件框架,这里的难点其实还是makefile文件的语法问题。下面介绍应用程序的makefile文件在实例中需要修改或增加的代码:
MCU=atmega16 /*微处理器的名字*/
TARGET=test /*应用程序文件名*/
GCCLIB=$(AVRX)/avrx/avrx.a
GCCINC=-L-I$(AVRX)/avrx-I$(AVR)/avr/inc /*加上相关的库*/
SCANF[_]LIB[_]MIN=-W1,-u,vfscanf-1scanf[_]min
SCANF[_]LIB[_]FLOAT=-W1,-u,vfscanf-1scanf[_]flt
SCANF[_]LIB /*设置sacnf函数库的类型,在不使用时可以注释掉,这样可以减小编译后的文件大小*/
LDFLAGS+=$(PRINTF[_]LIB)$(SCANF[_]LIB)$(MATH[_]LIB) /*新增的连接器参数设定*/
3 系统测试
3.1 系统实时性测试
在实时系统中,实时系统的实时性表现在系统对外部事件的响应能力上,系统通过中断来响应外部事件的发生,并且在用户中断程序中做的事要尽量少,把大部分工作留给任务去做,只是通过信号量或者信息机制来通知任务运行。Mega16的定时器2设为比较匹配输出模式,在匹配时间到了之后产生一定周期脉冲输出,并产生中断。设置定时器1为计数模式来计数产生的脉冲输出。通过定时器2的比较匹配中断服务子程序来发信号量通知任务运行,并在中断子程序中不开中断,而在任务得到信号后开中断,以实现中断处理与任务运行的同步,任务中对一个全局变量计数,以记录任务执行的次数。运行一段时间后,在设置的匹配时间里,任务的运行次数和定时器1的计数一样,则系统在这段时间里是能完全响应外部事件的,当定时器2的比较匹配时间设为大于23μs时,2个计数是相等的;当小于23μs时,定时器1计数值大于任务计数值,说明任务没有完全得到响应。这说明中断的进入和返回即系统对外部时间的响应和处理时间为23μs,远远大于其他操作系统在AVR单片机上移植后的响应时间。
3.2 使用例程测试
这里只对源文件中的几个例程先进行简单的编译,然后去掉不必要的代码,加入自己想测试的一些代码,进行了定时器控制模块,信号量和消息队列以其简单组合的测试,均在ATmega16上达到了预期的效果。
4 心得体会
①AVRX的源码都是用汇编语言编写的,相对来讲代码效率很高,但是由于没有详细的API介绍文档,所以最好的入门方法就是先读懂RTOS的源码和例程,然后进行修改,再加上自己的代码逐渐熟练应用。
②AVRX需要分配的堆栈为35个字节加上任务代码需要的额外堆栈,具体的大小取决于每个进程用的本地变量个数。比较好的确定分配给任务堆栈大小的方法是:分配很大的堆栈(如70字节)运行一段应用程序后看堆栈到多深(因为GCC启动时把所有内存都清0了,这样很容易看到)。不过,为了安全起见,用编译器或仿真器在估计堆栈的顶端写入几个字节的0xFFFFF去验证到底达到了多少字节,然后分配给比测试结果多两个以上的字节给这个任务。
③启动的最后一个指令必须跳转到Epilog()。
5 结论
AVRX是一个不错的RTOS,最显着的特点就是内核小,速度快,编译后大概只需500~700字节,且基本的调度功能一个也不少。由于其代码公开,结合不同型号AVR单片机的特性,可以在此基础上进行系统的裁减和扩展,使之能达到更好的效果,本文为AVR嵌入式系统的应用提供了借鉴。
AVR端口是真正的双向端口,不像51伪双向。这也是AVR的一项优势,只是操作时大家注意DDRn就可以了。真正双向端口在模拟时序方面不如伪双向的方便。
DDRn PORTn PINn 解释:n为端口号:ABCDE
DDRn:控制端口是输入还是输出,0为输入,1为输出。个人记忆方法:一比零大所以往外挤,即1为输出,0为输入。
PORTn:从引脚输出信号,当DDRn为1时,可以通过PORTn=x等端口操作语句给引脚输出赋值。
PINn:从引脚读输入信号,无论DDRn为何值,都可以通过x=PINn获得端口n的外部电平。
当引脚配置为输入时,若PORTxn 为"1“,上拉电阻将使能。内部上拉电阻的使用在键盘扫描的时候还要说到。
端口更详细功能及介绍以及端口第二功能请参考数据手册。
端口引脚配置
DDxn PORTxn PUD (in SFIOR) I/O 上拉电阻说明
0 0 X 输入 No 高阻态 (Hi-Z)
0 1 0 输入 Yes被外部电路拉低时将输出电流
0 1 1 输入 No高阻态(Hi-Z)
1 0 X 输出 No输出低电平 ( 漏电流)
1 1 X 输出 No输出高电平 ( 源电流)
如果有引脚未被使用,建议给这些引脚赋予一个确定电平。最简单的保证未用引脚具有确定电平的方法是使能内部上拉电阻。但要注意的是复位时上拉电阻将被禁用。如果复位时的功耗也有严格要求则建议使用外部上拉或下拉电阻。不推荐直接将未用引脚与VCC 或GND 连接,因为这样可能会在引脚偶然作为输出时出现冲击电流。
下面我们来看例子:
void port[_]init(void)
{
PORTA = 0x03;
DDRA = 0x03;
PORTB = 0x00;
DDRB = 0x01;
PORTC = 0x00;
DDRC = 0x00;
PORTD = 0x00;
DDRD = 0x00;// 建议赋值为零
}
PORTA = 0x03;DDRA = 0x03;这两句使PA口的PA1和PA0处于输出状态,PA7—PA2处于输入状态。这里的0x03即二进制的00000011,从左到右对应于Pn7--Pn0八个IO口。
通过跑马灯程序来深入理解IO口的操作:
CODE:
//ICC-AVR application builder : 2006-11-21 9:20:57
// Target : M32
// Crystal: 7.3728Mhz
#include
#include
void [_]delay(unsigned char n) //延时函数定义
{
unsigned char i,j;
for(;n!=0;n--) //n*10ms
{
for(j=100;j!=0;j--) //100us*100=10ms
{
for(i=147;i!=0;i--) //delay 100us
;
}
}
}
int main(void)
{
unsigned char i,j,k; //
PORTA=0xFF; //PA口设为输出高电平,灯灭
DDRA=0xFF; //PA口设置为输出
while(1)
{
i=1;
for (j=0;j
{
PORTA=~i; //反相输出,低电平有效,对应的灯亮
for (k=0;k
// 0b00000001 PA0
// 0b00000010 PA1
// 0b00000100 PA2
// 0b00001000 PA3
// 0b00010000 PA4
// 0b00100000 PA5
// 0b01000000 PA6
// 0b10000000 PA7
}
}
}[Copy to clipboard]
其他IO口操作指令:
void main(void)
{
PORTA=0xff;
DDRA=0xff; //输出 模式 ,IO口上拉电阻有效,1为输出,0为输入。
PORTA=0xf0; //等
以下三条指令只对操作符号右边的数字位是一的位操作。
PORTA&=~0x70; //清零 0x70为 01110000 ,即把*三位清零,其余数位不变。
PORTA|=0x77; //置一 0x77为 01110111 ,即把*210六位清零,其余数位不变。
PORTA^=0x70; //翻转 0x70为 01110000,即*三位,如果是零变成1,是一变成0。
(P & 0x80)==0x80; //按位与 判断p的第七位是否是一,是则成立
}
关于1
ADIF是一个寄存器变量,可以堪称数字4, 跟手册中的定义,包含芯片头文件的定义是一样的。
(1
ADCSR=(1
ADCSR|=(1
ADCSR&=~(1
while(ADCSR&(1
while(1)
{
while(ADCSR&(1
{
程序......
}
}
实践出真知:只看这样的说明是很枯燥的,从实践中去学习会是更好的途径,把这些代码都写到单片机里,一步一步调试运行,看看各个端口以及寄存器的效果,也锻练程序调试能力,和乐而不为呢?