随着电子技术的发展,现在在单片机的外围接口中,串行口由于其占用单片机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 < 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 因篇幅问题不能全部显示,请点此查看更多更全内容