基于AVR (ATmega16)单片机SPI接口计时器的设计



一、概述

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

二、硬件介绍

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

1、SPI外设接口

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

ATmega16 SPI 的特点如下:

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

?主机或从机操作

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

?种可编程的比特率

?传输结束中断标志

?写碰撞标志检测

?可以从闲置模式唤醒

?作为主机时具有倍速模式(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

 

位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

 

位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的控制寄存器,它用于选择定时器的时钟源,工作模式和比较输出的方式等。

位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

 

位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 <avr/io.h> 
#include <util/delay.h> 
#include<avr/interrupt.h>
#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 <<CS02) | (1 << CS00);  
// 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--;
}
}
} 
//================================================//


你可能感兴趣的:(基于AVR (ATmega16)单片机SPI接口计时器的设计)