一、概述
随着电子技术的发展,现在在单片机的外围接口中,串行口由于其占用单片机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 <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--;
}
}
}
//================================================//