hrefspace

 找回密码
 立即注册
搜索
热搜: PHP PS 程序设计
查看: 1240|回复: 0

自学AVR单片机十(8位数码管显示)

[复制链接]

275

主题

454

帖子

1014

积分

大司空

Rank: 5Rank: 5

积分
1014
发表于 2024-1-27 17:59:56 | 显示全部楼层 |阅读模式
动手学AVR单片机十、8位数码管显示的程序实现
我们接着来完成8位数码管的显示实验。现在我们开始动手编写程序;
根据前面的介绍,我们应该已经能够知道编写一个AVR单片机的C语言程序的基本步骤和方法了。
下面给出这个程序的主程序文件,在这个程序中我们应该能够知道这个程序都包含了那几块,具体来说我们应该能够在这个程序中把以下几个部分找出来:预编译语句、全局变量的定义、函数的声明、主函数、函数定义。如果你还不能够准确找出这几部分,那么需要把前面的内容再详细阅读一下。
  1. #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文件
  1. /*****************************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同时我们将与74HC595相关的函数定义部分放在74HC595.c文件中,如下/********************************74hc595.c**************************************/#include "hc595.h"//595端口初始化void HC595_port_init(void)          {PORTG = 0x00;DDRG |= (1 << PG0) | (1 << PG1) | (1 << PG2) | (1 << PG4);}//发送一个字节void HC595_Send_Data(unsigned char byte){unsigned char i; //CLR_HC595_latch;for(i = 0;i < 8;i++){    if(byte & 0x80)  {   SET_HC595_data;   }  else  {   CLR_HC595_data;  }  byte <<=1;    SET_HC595_sclk;         //上升沿数据移位  CLR_HC595_sclk;   }  SET_HC595_latch; CLR_HC595_latch;}//发送字符串void HC595_Output_Data(unsigned char data){CLR_HC595_latch;         //下降沿锁存器数据不变HC595_Send_Data(data);SET_HC595_latch;           //上升沿数据打入8位锁存器}
复制代码
在主程序中我们使用预定义语句将.h文件包含到了主程序文件中,那么我们怎样实现将.c文件编译到整个项目程序中呢?在这里我们只要在makefile文件中将这个.c文件加进去就可以了,如下图所示,在SRC = $(TARGET).C的后面空一格,然后输入我们所定义的.c文件的名称,然后保存makefile文件的更改。最后进行编译就可以了,编译的时候如果我们仔细观察编译器的输出信息,会发现不但编译了main.c文件,同时也编译了74hc595.c文件。


8位数码管显示的程序我们就这样编写好了,具体程序各个部分的功能在程序中都有注释,根据注释可以很容易的理解程序是怎样实现了8位数码管的显示。
最后想说的一点就是:在编写程序的时候要养成良好的习惯:给程序添加注释,这样一旦程序调试过程中出现错误,便于查找错误,另外有了注释可以使我们在很久以后看到这个程序也能很快想起以前的编程思路;第三点就是便于别人的阅读。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|hrefspace

GMT+8, 2024-11-1 13:23 , Processed in 0.063739 second(s), 21 queries .

Powered by hrefspace X3.4 Licensed

Copyright © 2022, hrefspace.

快速回复 返回顶部 返回列表