AVR单片机为了提高适应性和可靠性,增加了许多特色功能,诸如多时钟源、多功耗管理模式以及片内多钟类型存储器等。本章主要介绍的就是AVR单片机的这些功能及其使用方法。
6.1
时钟源
处理器必须有稳定的时钟才能可靠运行。AVR单片机的时钟配置比较灵活,既可以使用外部晶体振荡器、陶瓷谐振器或者RC振荡器作为时钟源,也可以直接使用来自外部的时钟信号,还可以使用片内的RC振荡器为系统提供时钟。多时钟源的优点在于可以充分利用不同时钟源的特点,使单片机的工作状态更加符合要求。
6.1.1
时钟的来源
获取时钟信号可以使用以下三种常用的方法:
1)RC振荡器。使用电阻和电容搭建成一个正反馈的振荡电路就可以获取时钟信号,通过改变电阻或电容的值可以调节时钟的频率。由RC网络构成的振荡电路优点是起振快,加电后立即输出满幅的振荡信号,但它也有缺点,当外界温度变化时,阻容元件的值也会发生变化,从而引起振荡频率的漂移。
2)陶瓷谐振器。陶瓷谐振器利用的是陶瓷的压电原理,一个两面安装有电极的陶瓷片在外加电场的作用下,陶瓷自身会发生形变而产生振荡,当达到自身的共振频率时,即可进入稳定的谐振状态。陶瓷谐振器的优点是起振快,但频率稳定性和精度稍差。
3)晶体振荡器。晶体振荡器简称晶振,其内部是一个经过精确切割的石英晶体,晶体两端连接有电极。石英晶体在受到激励后会输出特定频率的振荡信号,而且频率的稳定性极高。正是因为这一特点,使晶振作为一种廉价而稳定的时钟源得到了广泛的应用。同样,晶体振荡器也有缺点,一方面它在起振时是一个渐进的过程,需要经过一段时间才能输出稳定且满幅的振荡信号;另一方面就是在受到强烈撞击或电压冲击时,晶体会因物理结构的改变而损坏。晶体振荡器的外观如图6-1所示。
6.1.2
时钟的分布
ATmega32单片机的时钟分布如图6-2所示。从图中我们可以看出,可以为系统提供时钟的方法有多种,如外部RC振荡器、晶体振荡器、外部时钟、片内经校准的RC振荡器等。这些时钟不是全部运行的,而是根据需要有选择地为系统提供时钟。时钟选择开关用于将这些时钟源配置成系统时钟,经选定的时钟信号送入时钟控制单元,该单元用于为系统的CPU、RAM、ADC等单元分配时钟信号。
此外,ATmega32单片机的定时器2还可以外接独立的低功耗晶体作为自身的时基信号,使其可以不依赖于系统时钟而独立运行。为了确保系统的稳定,片内的看门狗电路也有自己独立的1MHz片内RC振荡器,当看门狗被使能后,独立的RC振荡器会为看门狗电路提供专用的时钟。
6.1.3
时钟的设置
ATmega32的时钟源是通过熔丝位进行设定的,二者的对应关系详见表6-1。值得注意的是,芯片在出厂时默认设置的时钟源是内部RC振荡器,当我们使用外部晶振作为主时钟源时,需要更改芯片的相关熔丝位,将时钟源切换到外部晶体振荡器上。更改时钟设置的方法详见第5章,时钟源及启动时间组合选项详见附录C。
1.晶体振荡器
ATmega32单片机的XTAL1与XTAL2引脚是片内振荡器的反向放大器输入端和输出端,用于连接外部晶体或陶瓷谐振器。CKSEL3:CKSEL0位用于调节放大器的增益以适应不同频率的晶体。熔丝位CKOPT用于使振荡器在输出引脚产生满幅度的振荡,这种模式适合于噪声环境,而且驱动晶振的频率范围比较宽。ATmega32单片机使用晶体振荡器时的典型电路如图6-3所示。
2.低频晶体振荡器
ATmega32单片机可以使用32.768kHz的时钟晶体作为时钟源,只要将熔丝位CKSEL3:CKSEL0设置为“1001”即可选择低频晶体振荡器。熔丝位CKOPT用于使能XTAL1和XTAL2引脚的内部电容,从而允许去除外部电容。当CKOPT位为0时,引脚内部36pF的电容被使能。
3.外部RC振荡器
在一些对时间不敏感的应用中,可以使用外部RC振荡器作为时钟源。ATmega32单片机使用外部RC振荡器时的典型电路如图6-4所示。
使用外部RC振荡器时的振荡频率可以通过下式计算:
f=1/(3RC)
在上面的计算公式中,电容C至少要选取22pF。当CKOPT位为0时,用户可以使能XTAL1和GND之间的片内36pF电容,从而无需外接电容。
4.片内RC振荡器
ATmega32单片机可以使用片内的RC振荡器作为时钟源,为系统提供1MHz、2MHz、4MHz和8MHz四种不同频率的时钟,熔丝位CKSEL3:CKSEL0用于设定内部RC振荡器的输出频率。为了修正片内RC振荡器的误差,可以通过编程OSCCAL寄存器对片内RC振荡频率进行微调,当振荡器输出频率为1MHz时,可以提供±3%的频率修正范围。
OSCCAL寄存器是ATmega32单片机的振荡器标定寄存器,将数值写入寄存器可以对内部振荡器频率进行微调。当OSCCAL寄存器的值为0时,振荡器以最低频率运行,当其值为0xFF时,振荡器以最高频率运行。
OSCCAL寄存器:振荡器标定寄存器
值得注意的是,ATmega32单片机在出厂时,已经对该振荡器在1MHz、2MHz、4MHz和8MHz时钟频率分别进行了校准,此校准值保存于单片机标识地址空间0x0000、0x0001、0x0002及0x0003的高位字节上。当系统复位时,1MHz的校准值会通过硬件自动加载到OSCCAL寄存器中以修正频率误差。如果需要其他频率标定值,则需通过编程器预先读取标定的数据字节,并将其保存到FLASH或E2PROM存储器中。程序运行后,软件读取相应的标定值并写入OSCCAL寄存器中以微调时钟频率。
5.外部时钟
外部时钟信号也可以作为单片机的时钟源。将熔丝位CKSEL3:CKSEL0设置为“0000”时,ATmega32单片机被配置成外部时钟模式,这时XTAL2引脚功能被禁止,外部时钟信号可以由XTAL1引脚输入。在此状态下,如果熔丝位CKOPT为0,则XTAL1引脚和GND之间的36pF电容被使能。外部时钟的连接方式如图6-5所示。
6.定时/计数器振荡器
ATmega32单片机可以在TOSC1和TOSC2引脚上连接32.768
kHz的时钟晶振作为定时/计数器的时钟源。使用此方式的优点是将定时/计数器与晶振一起构成了实时时钟(RTC),定时/计数器可以使用自己独立的时钟源运行,而无需依赖主时钟源的支持。在一些对功耗要求苛刻但又需要保持时间信息的应用中,可以将主振荡器关闭,仅使用定时/计数器时钟维持运行。
6.2
睡眠模式
为了降低功耗,ATmega32单片机使用睡眠模式来关闭不用的模块以减少电流消耗。按照所关闭模块的不同,可以将AVR单片机的睡眠模式细分为多种,不同种类的睡眠模式可以根据需要自行设定。
6.2.1
睡眠模式的分类
1.空闲模式
在此模式下CPU停止运行,而SPI、USART、模拟比较器、ADC、两线串行接口、定时/计数器、看门狗和中断系统继续工作。空闲模式只停止了CLK 2.ADC噪声抑制模式
在此模式下CPU停止运行,而ADC、外部中断、两线串行接口、定时/计数器2和看门狗继续工作。ADC噪声抑制模式停止了CLK 3.掉电模式
在掉电模式下,外部晶体停振,主时钟被禁用,只有外部中断、两线接口地址匹配及看门狗(如果使能的话)可以继续工作。外部复位、看门狗复位、BOD复位、两线接口地址匹配中断、外部中断INT0、INT1或INT2可以将单片机唤醒。
4.省电模式
省电模式与掉电模式唯一的不同在于,如果定时/计数器2使用自己独立的振荡器运行,则定时/计数器2在进入睡眠模式时仍然继续运行。除了掉电模式的唤醒方式外,定时/计数器2的溢出中断和比较匹配中断也可以将单片机唤醒。
5.Standby模式
Standby模式与掉电模式的不同之处是主振荡器在此模式下继续工作,唤醒时时钟不需要恢复,只需要6个时钟周期即可完成唤醒过程。
6.扩展Standby模式
扩展Standby模式与省、掉电模式的不同之处在于振荡器继续工作,其唤醒时间也只需要6个时钟周期。
这里需要注意的是,从施加掉电唤醒条件到真正唤醒有一个延迟时间,此时间用于时钟重新启动并稳定下来。唤醒周期可以由熔丝位CKSEL定义,不同种类睡眠模式的时钟使用情况及唤醒源详见表6-2。
6.2.2
睡眠模式的应用
MCUCR寄存器用于对睡眠模式的控制,其中包含了多个与电源管理相关的控制位。
MCUCR寄存器:CPU的控制寄存器
bit 7SE:休眠使能。该位置1时允许进入睡眠模式。为了防止干扰,可以在SLEEP指令之前将SE位置1,单片机一旦唤醒可以立即清零控制位SE。
bit 6-4SM2:SM0:睡眠模式选择位,具体设置详见表6-2。
bit 3-0暂不介绍。
ATmega32单片机进入睡眠模式的方法是编程MCUCR寄存器,将休眠使能位SE置1,并且选择合适的睡眠状态,在接下来的代码中执行SLEEP指令,单片机即可进入睡眠状态。使单片机进入睡眠状态可以参考以下代码:
MCUCR|=0xA0
;
SLEEP()
;
为了降低功耗,除了选择合适的睡眠模式以外,还需要考虑诸如模拟比较器、掉电检测器BOD、片内电压基准源、看门狗等模块也会消耗电流,如果系统无需此功能,应该尽量将其关闭。另外,置为输出的I/O口应尽量避免驱动阻性负载,置为输入的I/O口也不要悬空,否则会消耗额外的电流。如果片上调试系统被使能,当芯片进入掉电或省电模式时,主时钟会保持运行,应当考虑在熔丝位中将其禁止。
6.3
片内存储器
下面我们要一起来探讨ATmega32单片机的片内存储器。电子工业制造水平的进步使在一个芯片内部集成多个种类的存储器成为可能,不同种类的存储器有着不同的特性,因而其用途也不相同。ATmega32单片机的片内存储器有以下三种:
·SRAM存储器:特点是存取速度快,掉电后数据会消失,所以被用于保存单片机运行过程的中间数据及变量。
·FLASH存储器:基于闪存工艺制造,具有电可擦除且掉电后长期保存数据的特性,用于保存程序的指令代码。
·E2PROM存储器:同样具有电可擦除以及掉电后可长期保存数据的性能,因为它的耐擦写能力比FLASH存储器高一个数量级,所以用来保存程序中的状态信息(掉电后不丢失)或需要频繁调用的数据等。
6.3.1
存储器的结构
ATmega32单片机将片内的存储区划分成两个主要的存储器空间:数据存储器空间和程序存储器空间,而E2PROM存储器空间是独立存在的。存储器的构成详见表6-3。
1.数据存储器
AVR单片机在设计时把内部工作寄存器(通用寄存器和I/O寄存器)在逻辑上也划分在了RAM空间中,这样做的好处是既可以使用专用的寄存器指令对寄存器进行操作,又可以将寄存器当作RAM使用,方便了程序的设计。ATmega32单片机的数据存储器构成如图6-6所示。
从图中我们不难发现,32个通用工作寄存器和64个I/O寄存器被直接映射到数据空间的前96个地址上,之后的地址才是2048字节的内部数据SRAM。通用工作寄存器的作用是可以取代传统意义上的累加器,从而避免了由于累加器和存储器之间频繁的数据交换而出现的瓶颈现象,提高了单片机指令运行的速度。I/O寄存器用于对MCU的功能控制和数据交换,如I/O口的状态、定时/计数器、A/D转换器的控制等。
2.程序存储器
程序存储器由32K字节可在线编程的FLASH存储器构成,用于存放程序指令代码。ATmega32单片机所有指令均为16位或32位,所以32K字节的FLASH存储器组织成16K×16位的形式以方便读取。不便于功能的区分,将程序存储器分成两个不同的区,即引导程序区(BOOT区)和应用程序区,其结构如图6-7所示。
通过引入BOOT区的概念,使单片机在复位后能直接访问引导区并装入启动程序,实现自我编程的功能,这无疑会使产品的在线升级成为可能。
6.3.2
E ATmega32单片机集成了1024字节的E 1.EEARH和EEARL寄存器
此寄存器是E EEARH寄存器:E EEARL寄存器:E bit 15-10未用位。
bit 9-0EEAR9:EEAR0:E 2.EEDR寄存器
该寄存器是E EEDR寄存器:E bit 7-0EEDR7:EEDR0:E 3.EECR寄存器
该寄存器是E EECR寄存器:E bit 7-4未用位。
bit 3EERIE:E bit 2EEMWE:E bit 1EEWE:E bit 0EERE:E 注意:执行E 6.3.3
E 对E 1)等待上一次写操作结束(EEWE位为0)。
2)将目标E 3)将EECR寄存器的EERE位置1启动读操作。
为了防止意外对E 1)等待上一次写操作结束(EEWE位为0)。
2)将目标E 3)将新的E 4)对EECR寄存器的EEMWE位写1,打开写使能。
5)在置位EEMWE的4个时钟周期内,将EEWE位置1启动写操作。
注意:为了避免写操作失败,建议在对E ATmega32单片机在片内集成E 打开Atmel Studio 6.1软件,新建名为“E 代码清单6-1
E2PROM读写测试
/*
*
E2PROM.c *
*
Created: 2013/10/23
21:33:16
*
Author: GAO */
/*
E2PROM 读写测试
数码管显示读出数据
断电可接力
*/
#include
//
包含AVR 头文件
#define F_CPU 16000000UL //
定义系统时钟
#include
//
包含延时函数头文件
unsigned char table0[]={0x3f,0x06,0x5b,0x4f,0x66, 0x6d,0x7d,0x07,0x7f,0x6f }; //
共阴无点
unsigned char table1[]={0xbf,0x86,0xdb,0xcf,0xe6, 0xed,0xfd,0x87,0xff,0xef }; //
共阴有点
unsigned char NUM; //
定义全局变量NUM 用于显示
void display(unsigned int NUM); //
数码管显示函数声明
unsigned char read_eeprom(unsigned int EPADD); //
读eeprom 函数声明
void write_eeprom(unsigned int EPADD,unsigned char EPDATA); //
写eeprom 函数声明
/**********
主函数**********/
int main(void)
{
DDRA=0xFF; //
设数码管段驱动端为输出
DDRB=0xF0; //
设数码管位驱动端为输出
while(1)
{
NUM=read_eeprom(1); //
从"1"
存储单元读出一个字节
NUM++; _delay_ms(10); //
延时10ms write_eeprom(1,NUM); //
将NUM 值写入"1"
存储单元
display(NUM); //
扫描数码管
}
}
/**********
数码管显示函数**********/
void display(unsigned int NUM)
{
unsigned char NUM4,NUM3,NUM2,NUM1; NUM1=NUM%10; NUM2=NUM%100/10; NUM3=NUM%1000/100; NUM4=NUM/1000; PORTA=table0[NUM1]; PORTB=0x10; _delay_ms(2); //
延时2ms PORTA=0x00; PORTB=0x00; _delay_ms(1); //
延时1ms PORTA=table0[NUM2]; PORTB=0x20; _delay_ms(2); //
延时2ms PORTA=0x00; PORTB=0x00; _delay_ms(1); //
延时1ms PORTA=table0[NUM3]; PORTB=0x40; _delay_ms(2); //
延时2ms PORTA=0x00; PORTB=0x00; _delay_ms(1); //
延时1ms PORTA=table0[NUM4]; PORTB=0x80; _delay_ms(2); //
延时2ms PORTA=0x00; PORTB=0x00; _delay_ms(1); //
延时1ms }
/**********
读eeprom 函数**********/
unsigned char read_eeprom(unsigned int EPADD)
{
while((EECR&0x02)==1); //
等待上一次写操作结束
EEAR =
EPADD; //
设置地址寄存器
EECR |=0x01; //
启动读操作
return EEDR; //
返回读出的数据
}
/**********
写eeprom 函数**********/
void write_eeprom(unsigned int EPADD,unsigned char EPDATA)
{
while((EECR&0x02)==1); //
等待上一次写操作结束
EEAR =
EPADD; //
设置地址寄存器
EEDR =
EPDATA; //
设置数据寄存器
EECR |=
0x04; //
置位EEMWE 打开写使能
EECR |=
0x02; //
置位EEWE 以启动写操作
}
/**********
结束**********/
以上代码成功编译后,下载到AVR系统板的目标单片机中,程序运行后效果如图6-8所示,数码管的显示数值会从0~255不断循环累加,而且掉电后数值会保存下来。