您的当前位置:首页正文

定时器

2024-01-16 来源:客趣旅游网
一、概述

随着电子技术的发展,现在在单片机的外围接口中,串行口由于其占用单片机IO口的资源少而得到设计人员的青睐,其中SPI是AVR单片机自身硬件带有的串行外设接口之一。我们利用具有SPI协议的MAX7219芯片控制数码管,可以减少对单片机IO口的占用,加上ATmega16的定时/计数器功能,我们很容易就能设计一个简单的计时器,我们还可以加上按键来控制时间。这也是AVR单片机初学者学习SPI接口和定时器计数器的很好的一个小项目,下面就让我们开始我们的设计之旅吧,你肯定会有所收获的。。。。。。

二、硬件介绍

本设计的硬件原理图很简单,就是一个AVR单片机的最小系统和MAX7219的典型应用电路组成。在这里就不再给大家了,如果需要可以自己查阅相关资料或与本人联系。

1、SPI外设接口

串行外设接口SPI 允许ATmega16 和外设或其他AVR 器件进行高速的同步数据传输。

ATmega16 SPI 的特点如下:

?全双工, 3 线同步数据传输

?主机或从机操作

?LSB 首先发送或MSB 首先发送

?7 种可编程的比特率

?传输结束中断标志

?写碰撞标志检测

?可以从闲置模式唤醒

?作为主机时具有倍速模式(CK/2)

系统包括两个移位寄存器和一个主机时钟发生器。通过将需要的从机的 SS 引脚拉低,主机启动一次通讯过程。主机和从机将需要发送的数据放入相应的移位寄存器。主机在SCK 引脚上产生时钟脉冲以交换数据。主机的数据从主机的MOSI 移出,从从机的MOSI 移入;从机的数据从从机的MISO 移出,从主机的MISO 移入。主机通过将从机的SS 拉高实现与从机的同步。

配置为SPI 主机时, SPI 接口不自动控制 SS 引脚,必须由用户软件来处理。 对 SPI 数据寄存器写入数据即启动SPI 时钟,将8 比特的数据移入从机。传输结束后SPI 时钟停

止,传输结束标志SPIF 置位。如果此时SPCR 寄存器的SPI 中断使能位SPIE 置位,中断就会发生。主机可以继续往SPDR 写入数据以移位到从机中去,或者是将从机的SS拉高以说明数据包发送完成。最后进来的数据将一直保存于缓冲寄存器里。

注:本设计中我们配置为主机模式,其他的资料就不再叙述了,更详细的SPI接口介绍请查阅ATmega16的DATASHEET.

2、定时/计数器

(1)定时/计数器的简单介绍

定时/计数器(Timer/Counter)是单片机中最基本的接口之一,它的用途非常广泛,常用于计数、延时、测量周期、频率、脉宽、提供定时脉冲信号等。在实际应用中,对于转速,位移、速度、流量等物理量的测量,通常也是由传感器转换成脉冲电信号,通过使用定时/计数器来测量其周期或频率,再经过计算处理获得。

相对于一般8位单片机而言,AVR不仅配备了更多的定时/计数器接口,而且还是增强型的,如通过定时计数器与比较匹配寄存器相互配合,生成占空比可变的方波信号,即脉冲宽度调制输出PWM信号,用于D/A、马达无级调速控制、变频控制等,功能非常强大。

ATmega16一共配置了2个8位和1个16位,共3个定时/计数器,它们是8位的定时计数器T/C0、T/C2和16位的定时/计数器T/C1。在接下来的设计中,我们将学习这些定时/计数器的各种功能和使用方法。

在本设计中,我们利用ATmega16单片机的定时/计数器0的计数功能,在编写程序时通过定时/计数器中断0来实现定时计数的功能。

(2)T/C0实现定时功能的寄存器设置

在本设计中我们只列出T/C0实现定时计数功能的寄存器相关位的设置

① T/C0计数寄存器—TCNT0 位 7 6 5 4 3 2 1 0

读/写 R/W R/W R/W R/W R/W R/W R/W R/W

初始化值 0 0 0 0 0 0 0 0

TCNT0是T/C0的计数值寄存器,该寄存器可以直接被MCU读写访问。写TCNT0寄存器将在下一个定时器时钟周期中阻塞比较匹配。因此,在计数器运行期间修改TCNT0的内容,有可能将丢失一次TCNT0与OCR0的匹配比较操作。

② 定时计数器中断屏蔽寄存器—TIMSK 位 7 6 5 4 3 2 1 0

TOIE0

读/写 R/W

初始化值 0 0 0 0 0 0 0 0

l 位0— TOIE0:T/C0溢出中断允许标志位

当TOIE0被设为“1”,且状态寄存器中的I位被设为“1”时,将使能T/C0溢出中断。若在T/C0上发生溢出,即TOV0=1时,则执行T/C2(T/C0)溢出中断服务程序。

③ 定时计数器中断标志寄存器—TIFR 位 7 6 5

4 3 2 1 0

TOV0

读/写 R/W R/W R/W R/W R/W R/W R/W R/W

初始化值 0 0 0 0 0 0 0 0

l 位0— TOV0: T/C0溢出中断标志位

当T/C0产生溢出时, TOV0位被设为“1”。当转入T/C0溢出中断向量执行中断处理程序时, TOV0由硬件自动清零。写入一个逻辑“1”到TOV0标志位将清除该标志位。当寄存器SREG中的I位、TOIE0以及TOV0均为“1”时, T/C0的溢出中断被执行。

④ T/C0控制寄存器—TCCR0 位 7 6 5 4 3 2 1 0

$33($0053)

WGM00

WGM01 CS02 CS01 CS00 TCCR0

读/写 R/W R/W R/W R/W R/W R/W R/W R/W

初始化值 0 0 0 0 0

0 0 0

8位寄存器TCCR0是T/C0的控制寄存器,它用于选择定时器的时钟源,工作模式和比较输出的方式等。

l 位3,6—WGM0[1:0]:波形发生模式

这两个标志位控制T/C0的计数和工作方式,计数器计数的上限值,以及确定波形发生器的工作模式(见下表)。T/C0支持的工作模式有:普通模式,比较匹配时定时器清零(CTC)模式,以及两种脉宽调制(PWM)模式。

T/C0的波形产生模式

模 式 WGM01 WGM00

T/C0工作模式 计数上限值 OCR0更新 TOV0置位 0 0 0

普通模式 0xFF 立即 0xFF 1 0 1

PWM,相位可调 0xFF 0xFF 0x00

2 1 0

CTC模式 OCR0 立即 0xFF 3 1 1

快速PWM 0xFF 0xFF 0xFF

l 位2,0—CS0[2:0]:T/C0时钟源选择

这3个标志位被用于选择设定T/C0的时钟源,见下表。

T/C0的时钟源选择

CS02 CS01 CS00 说 明 0 0 0

无时钟源(停止T/C0) 0 0 1

clkT0S(不经过分频器) 0

1 0

clkT0S/8(来自预分频器) 0 1 1

clkT0S/64(来自预分频器) 1 0 0

clkT0S/256(来自预分频器) 1 0 1

clkT0S/1024(来自预分频器) 1 1 0

外部T0脚,下降沿驱动 1 1 1

外部T0脚,上升沿驱动

3、数码管控制芯片MAX7219

MAX7219是MAXIM公司生产的串行输入/输出共阴极电流型动态扫描LED驱动片,SPI接口支持10M传输速率,而且其片内包含有一个BCD码到七段码译码器,各位数字可被寻址和更新,动态扫描显示,从而节省了很多CPU资源。MAX7219可以驱动8位7段数字LED显示器,可设定数码管个数和显示亮度,而且外围电路简单,只需一个外部电阻来设置所有LED的段电流。MAX7219为窄DIP24封装。其典型电路和各引脚功能请查阅MAX7219的DataSheet.

注:这里由于我本人以前做的数码管模块只有5个数码管,为了节省时间,不专门为本设计另外制作显示模块了,所以在本设计中我们没有用8个数码管,在程序设置上我们也设置为5个数码管显示。

三、程序设计

利用定时/计数器的定时功能产生1S定时中断,用数码管显示时间,实现计数器的功能

/**********************************************************

作者:痞子飞

编译环境:AVR STUDIO + GCC

时间:2010年6月

注:本程序中的键盘程序引用于刘海成老师的《AVR单片机原理与测控工程应用》一书,在此感谢刘海成老师

**********************************************************/

详情请进:http://q.163.com/longfei-mcu/

#include

#include

#include

#define SPI_DDR DDRB

#define SPI_PORT PORTB

#define SPI_SS PB4

#define SPI_MOSI PB5

#define SPI_SCK PB7

unsigned char second,fen;

char fen_ge,fen_shi,second_ge,second_shi,samp;

volatile unsigned char Counter;

//1S计时变量,如果在中断中调用全局变量,必须加

//volatile来定义,否则变量不会变化

//==============MAX7219 AVR SPI====================//

#define REG_DECODE 0x09 /*译码方式寄存器 */

#define REG_INTENSITY 0x0a /*亮度寄存器 */

#define REG_LIMIT 0x0b /*扫描界限寄存器 */

#define REG_SHUTDOWN 0x0c /*停机寄存器

#define REG_DISPTEST 0x0f /*显示测试寄存器

extern void InitMax7219(void);

extern void UpdataMax7219(unsigned char ucDig , unsigned char ucSeg);

static void InitSpi(void);

static void SendWord(unsigned char ucDataH , unsigned char ucDataL);

//===========SPI初始化=======================//

static void InitSpi(void) {

SPI_DDR |= (1 << SPI_SS) | (1 << SPI_MOSI) | (1 << SPI_SCK);

SPI_PORT |= (1 << SPI_SS);

// 使能SPI,主机模式,上升沿采样,16分频

SPCR |= (1 << SPE) | (1 << MSTR) | (1 << SPR0);

SPSR = 0x00;

*/ */ }

//==============SPI写字节函数=================//

static void SendWord(unsigned char ucDataH , unsigned char ucDataL) {

SPI_PORT &= ~(1 << SPI_SS);

SPDR = ucDataH;

while(!(SPSR & (1 << SPIF)));

SPDR = ucDataL;

while(!(SPSR & (1 << SPIF)));

SPI_PORT |= (1 << SPI_SS); }

//=============Max7219初始化=================//

void InitMax7219(void) {

InitSpi();

SendWord(REG_DECODE,0xff); //译码选择

SendWord(REG_INTENSITY,0x0f); //亮度选择最亮

SendWord(REG_LIMIT,5); //LED个数选择为5个

SendWord(REG_SHUTDOWN,0x01);//启动工作

SendWord(REG_DISPTEST,0x00); //不测试 }

//=============往MAX7219里写入数据======================//

void UpdataMax7219(unsigned char ucDig , unsigned char ucSeg) {

SendWord(ucDig , ucSeg); }

//==========TC0初始化寄存器==============//

void TC0_init() {

TIMSK |= (1 << TOIE0); //T/C0溢出中断允许

TCCR0 |= (1 <// T/C0工作于普通模式,1024分频,定时器频率 = 1M/1024 = //976.5625Hz

TCNT0 = 12;

//定时初值设置,定时时间 = (256-12)/976.5625=249.856ms

Counter = 0; // 1S计时变量清零 }

//===============T/C0定时中断服务程序====================//

ISR(TIMER0_OVF_vect ) {

TCNT0 = 12; //重装计数初值

if(++Counter >= 4)

//定时时间到1S吗?定时中断溢出4次为1S

{

second++;

Counter = 0; //1S计时变量清零

if(second==60) {

fen++;

second = 0; } }

second_ge = second%10;

second_shi = (second/10)%10;

samp=0x0a;

fen_ge = fen%10;

fen_shi = (fen/10)%10;

UpdataMax7219(0x05,second_ge);

UpdataMax7219(0x04,second_shi);

UpdataMax7219(0x03,samp);

UpdataMax7219(0x02,fen_ge);

UpdataMax7219(0x01,fen_shi); }

//=======================================================//

//=================键盘程序设计(智能)==============//

unsigned char key_value;

unsigned char Read_key(void) {

static unsigned char last_key = 0xff;

static unsigned int key_count = 0;

#define c_wobble_time 300//去按键抖动时间(待定)

#define c_keyover_time 30000//等待按键进入连击模式时间(待定)

#define c_keyquick_time 2000//等待按键抬起时间(待定)

static unsigned keyover_time = c_keyover_time;

unsigned char nc;

nc = PIND&0X0F;//读按键,PD0~PD3

if(nc==0x0f) {

key_count = 0;

keyover_time = c_keyover_time;

return 0xff;//无按键按下返回0XFF } else {

if(nc==last_key) {

if(++key_count==c_wobble_time)

return nc;//去抖动结束,返回按键值 else {

if(key_count>keyover_time)

//等待按键抬起时间技术并进入连击模式 {

key_count = 0;

keyover_time = c_keyquick_time;//将处于连击模式 }

return 0xff; } } else {

last_key = nc;

key_count = 0;

keyover_time = c_keyover_time;

return 0xff; } } }

//=======================================================//

//======================主函数服务程序==================//

int main() {

DDRD = 0X00;

PORTD = 0X0F;

InitMax7219();

TC0_init();

sei(); //使能全局中断

while(1) {

//第一个键的值为0x0e,第二个键值为0x0d;第三个键值为0x0b;第四个键值为0x07

key_value = Read_key();

if(key_value==0x0e)//按下第一个键,秒加 {

second++;

if(second==60) {

second=0;

fen++; }

}

if(key_value==0x0b)//按下第三个键,分钟加 {

fen++; }

if(key_value==0x0d)//按下第二个键,秒减 {

second--;

if(second==0) {

second=59;

fen--; } }

if(key_value==0x07)//按下第四个键,分钟减 {

fen--; } } }

//================================================// 本文来自电子工程师之家:http://www.eehome.cn/read.php?tid=41318

因篇幅问题不能全部显示,请点此查看更多更全内容