本帖最后由 wangwo 于 2009-12-20 22:54 编辑
动手学AVR单片机十、8位数码管显示的程序实现
我们接着来完成8位数码管的显示实验。现在我们开始动手编写程序;
根据前面的介绍,我们应该已经能够知道编写一个AVR单片机的C语言程序的基本步骤和方法了。
下面给出这个程序的主程序文件,在这个程序中我们应该能够知道这个程序都包含了那几块,具体来说我们应该能够在这个程序中把以下几个部分找出来:预编译语句、全局变量的定义、函数的声明、主函数、函数定义。如果你还不能够准确找出这几部分,那么需要把前面的内容再详细阅读一下。
主程序代码
#include <avr/io.h> //io端口寄存器配置文件,必须包含
#include <util/delay.h> //GCC中的延时函数头文件
#include "hc595.h"
//unsigned char Led_Disbuf[10]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F}; //共阴极
unsigned char Led_Disbuf[10]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90}; //共阳极
unsigned char ComBuf[8] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
//函数声明
extern void Delayus(unsigned int lus); //us延时函数
extern void Delayms(unsigned int lms); //ms延时函数
int main(void) //GCC中main文件必须为返回整形值的函数,没有参数
{
unsigned char i;
PORTB = 0xff; //PORTB输出低电平,使LED熄灭
DDRB = 0xFF; //配置端口PB全部为输出口
HC595_port_init();
while(1)
{
for(i = 0; i < 8;i++)
{
PORTB = Led_Disbuf; //送段码
HC595_Send_Data(ComBuf); //选通位选端口
Delayus(70); //延时
HC595_Send_Data(0x00); //位选通关闭
}
}
}
//us级别的延时函数
void Delayus(unsigned int lus)
{
while(lus--)
{
_delay_loop_2(4); //_delay_loop_2(1)是延时4个时钟周期,参数为4则延时16
//个时钟周期,本实验用16M晶体,则16个时钟周期为16/16=1us
}
}
//ms级别的延时函数
void Delayms(unsigned int lms)
{
while(lms--)
{
Delayus(1000); //延时1ms
}
}
在这个主程序文件中有人可能会注意到有一些我们前面没有介绍过的内容,比如extern这个单词,它在这里起什么作用?
这就牵涉到C语言的关键字了,关于C语言的关键字,我们可以到相关的C语言教材中去做详细了解,在这里我们只针对extern这个关键字作出解释,我们注意到extern用在一个函数声明的地方,它的作用就是把这个函数声明为外部函数,这样我们在整个项目的所有文件中就都可以调用这个函数了。同理extern也可以用来声明一个变量为外部变量。
我们可能还会有一个疑惑:#include "hc595.h"这个头文件包含语句中的hc595.h有什么作用,它是GCC提供的还是我们自己编写的?
这里我们就要学习C语言中的一个重要的概念:模块化程序设计。何为模块化程序设计?它实现什么功能?如果要铺开来讲的话,可能需要一本书的内容。我们耗不起这个时间和精力。其实我们只需要知道,模块化程序设计是为了简化程序容量而采取的一种将一个程序分成不同的模块,然后通过特定的方法将这些模块组合起来共同完成同一个目标。通俗的说就是化整为零。
在我们刚开始学习单片机的时候,我们编写的程序都很简单,程序量也不大,所以往往涉及不到模块化程序设计,但是这是一种很好的编程思路,我们有必要掌握。本实例就是采用的这种方法。
模块化程序设计的思路是:将实现相同功能的程序单独编写,然后实现一个综合的功能,举个例子,我们想实现一个液晶显示的温度测量程序,那么我们可以把液晶显示相关的程序放在一个文件中,把温度测量的程序放在另一个文件中,最后在主程序中调用这两个文件来实现整体的功能。
通常我们在进行模块化程序设计的时候,常常将变量定义,端口设置,函数声明等部分保存在一个.h文件中,而将函数定义部分放在一个.c文件中,在编写主程序文件的时候,用预处理命令#include将.h文件包含起来,而在编译的时候将所有用到的.c文件一起编译。这样就实现了模块化文件的整合。
在本实例中,我们将74HC595相关的变量定义,端口定义,函数声明放在74HC595.h文件中,而主程序中的#include "hc595.h"这句话实现了将这个文件包含到主程序中的功能。
下面是本实例中模块化程序设计的.h文件
/*****************************
74hc595.h
***********************************/
/*74hc595与单片机的引脚连接
/MR(10脚) VCC 低点平时将移位寄存器的数据清零。通常将它接Vcc
/OE(13脚) PG4 高电平时禁止输出(高阻态)。
如果单片机的引脚不紧张,用一个引脚控制它,
可以方便地产生闪烁和熄灭效果。比通过数据端移位控制要省时省力。
ST_CP(12脚) PG1 上升沿时移位寄存器的数据进入数据存储寄存器,
下降沿时存储寄存器数据不变。通常将RCK置为低电平,
当移位结束后,在RCK端产生一个正脉冲(5V时,大于几十纳秒就行了。
通常都选微秒级),更新显示数据。
SH_CP(11脚) PG0 上升沿时数据寄存器的数据移位。QA-->QB-->QC-->...-->QH;
下降沿移位寄存器数据不变。(脉冲宽度:5V时,大于几十纳秒就行了。
通常都选微秒级)
DS(14) PG2 串行数据输入端。
*/
#ifndef __HC595_H__
#define __HC595_H__
#include <avr/io.h> //io端口寄存器配置文件,必须包含
#include <util/delay.h> //GCC中的延时函数头文件
#define HC595_latch (1 << PG1) //上升沿数据打入8位锁存器,下降沿锁存器数据不变
#define HC595_sclk (1 << PG0) //上升沿数据移位,下降沿数据不变
#define HC595_oe (1 << PG4) //低电平,8位数据锁存器输出,高电平输出高组态
#define HC595_data (1 << PG2) //串行数据输入端
#define SET_HC595_latch (PORTG |= (1 << PG1))
#define CLR_HC595_latch (PORTG &= ~(1 << PG1))
#define SET_HC595_sclk (PORTG |= (1 << PG0))
#define CLR_HC595_sclk (PORTG &= ~(1 << PG0))
#define SET_HC595_data (PORTG |= (1 << PG2))
#define CLR_HC595_data (PORTG &= ~(1 << PG2))
#define SET_HC595_oe (PORTG |= (1 << PG4))
#define CLR_HC595_oe (PORTG &= ~(1 << PG4))
void HC595_port_init(void); //595端口初始化
void HC595_Send_Data(unsigned char byte); //发送一个字节
void HC595_Output_Data(unsigned char data); //发送字符串
#endif |