ATmega8仿真——LED 数码管的学习

1. I/O 口的结构及特点

  • Atmega8 有23 个I/O 引脚,分成3 个8 位的端口B、C 和D,其中C 口只有7 位
  • Atmega8 采用3个8位寄存器来控制I/O端口,它们分别是:方向寄存器DDRx、数据寄存器PORTx、输入引脚寄存器PINx

  x为B或C 或D,分别代表B口、C口或D 口;

  n为0~7,代表寄存器中的位置;

  其中DDRx 和PORTx 是可读写寄存器,而PINx 为只读寄存器;

  每个I/O引脚内部都有独立的上拉电阻电路,可通过程序设置内部上拉电阻是否有效。

方向寄存器DDRx中的每个位用于控制I/O口一个引脚的输入输出方向,即控制I/O口的工作模式为输出模式还是输入模式。

  • 当DDRxn=1 时,I/O 的Pxn 引脚处于输出模式。此时

    若PORTxn=1时,I/O引脚呈高电平,同时可提供输出20mA 的电流;

    若PORTxn=0时,I/O引脚呈低电平,同时可吸收20mA 的电流。

  • 当 DDRxn=0 时,I/O 的Pxn 引脚处于输入模式。此时

    引脚寄存器PINxn 中的数据就是外部引脚的实际电平;

    此时可通过PORTxn 的设置可控制内部的上拉电阻使用或不使用。

注意事项:

  1. 使用AVR的I/O 口,首先应正确设置其工作模式(输入模式还是输出模式),设置DDRx;
  2. 当I/O工作在输入模式(DDRxn=0)时,读取引脚上的电平应取PINxn的值,而不是PORTxn的值;
  3. 当I/O口工作在输入模式(DDRxn=0)时,应根据实际情况设置内部上拉电阻,利用内部上拉电阻可以节省外部上拉电阻;
  4. 将I/O空工作模式由输出模式设置为输入模式后,必须等待一个时钟周期后才能正确的读到外部引脚的值

2. 跑马灯程序控制发光二极管

   我们选择用PD0~PD7来控制8个发光二极管循环点亮,从而实现“跑马灯”

所以电路图如图所示:

C程序如下:

 1 //延时1MS
 2 void delay_1ms()
 3 {
 4   unsigned int i;
 5   for(i=1;i<(unsigned int)(1144-2);i++)
 6     ;
 7 }
 8 //延时nMS
 9 void delay_ms(unsigned int n)
10 {
11   unsigned int i=0;
12   for(i=0;i<n;i++)
13   {
14     delay_1ms();
15   }
16 }
17 //主函数,依次顺序打开LED
18 int main()
19 {
20   unsigned char i;
21   DDRD = 0xFF; //设置D口为输出模式
22   PORTD = 0xFF; //置高电平
23   while(1)
24   {
25     for(i=0;i<8;i++) //顺序打开LED
26     {
27       PORTD = ~(1<<i); //点亮的位置低电平
28       delay_ms(200);
29     }
30   }
31   return 0;
32 }

问:如果改变 delay_ms 的时间会变的怎样呢?

  => 跑马灯的变换速度改变。

问:如果直接让 PORTD = ( 1<<i )呢?

  => PORTD = ~( 1<<i ):每次只亮一个灯;

  => PORTD = ( 1<<i ):每次只有一个灯是不亮的,其余灯都亮着。

问:能不能添加或修改程序,改变灯亮的顺序和时间呢?

  => 改变循环条件就可以改变亮灯的顺序,如把i++改为i+=2,即可实现隔一盏灯亮一个;

  => 亮灯时间改变延迟时间delay_ms()的传入参数即可。

3. 单个LED数码管练习

给数码管的a、b、c、d、e、f、g七个发光二极管加不同的电平,二极管显示不同亮暗的组合就可以显示不同的字形;

以1为高电平,0为低电平,给出字形码表:

即0x3F表示的就是字型‘0’,0x06表示的就是字型‘1’......;

所以直接把这种对应关系存到一个Char型数组里(一个Char型是8位);

想要对应的a、b、c、d、e、f、g七个发光二极管展示亮与暗,我们选用PD0~7这8位来控制;

如:想要展示字型‘0’ => ‘0’对应字形码是0x3F => 其中发光二极管的a~f均为亮状态 => PD0~5均为低电平(低电平亮灯)。

我们用一个LED数码管显示数字0~9,仍然使用PD口控制,循环显示数字

电路图如图所示:

了解理论知识后便可以开始编程:(延时函数与上面相同)

 1 void main()
 2 {
 3     unsigned char i;
 4     unsigned char num[10] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
 5     
 6     DDRD = 0xFF; //设置D口为输出模式
 7     PORTD = 0xFF; //置高电平
 8     
 9     while(1)
10     {
11         for(i=0;i<10;i++) //LED数码管顺序显示字型0~9
12         {
13             PORTD = num[i];
14             delay_ms(500);
15         }
16     }
17 }

问:想显示A~F 怎么办呢?

  => num[] 数组再添加多几个元素,分别是A~F对应的字形码。

问:能不能显示像H,L 一类的字母呢?

  => 能,想要显示字母H,只需要二极管的b、c、e、f、g亮即可;

  => 想要显示字母L,只需要二极管的d、e、f亮即可。

问:改变了delay_ms 函数的延时时间会怎样呢?

  => 数字之间的显示间隔边长。

问:要显示小数点我们应该怎么办呢?

  => 在字形码表中可以看出,最高位h是没有作用的,所以可以用h位来控制小数点,当最高位为1时小数点亮,为0时不亮;

  => 如0x5B表示‘2’,那么0x5B + 0x80表示‘2.’。

4.多个LED数码管实验

静态显示:3小节的内容便是静态展示

动态显示:采用各数码管循环轮流的显示的方法,当循环频率较高时,利用人眼的暂留特性,感觉不到数码管的闪烁,就像看到数码管在同时发光一样,类似电影的原理。

两者对比:动态显示比静态显示占用资源少,耗电少;但是稳定性却差,程序设计也更为复杂,MCU负担重。

动态显示需要一个接口完成字形码的输出,另外一个接口完成各数码管的轮流显示;

我们要实现从“000.0”到“999.9”的数字变化显示过程;

用PB口做字形码的输出口,用PC0~PC3控制数码管的轮转流显示;

从左数起,其中PC0表示第0个数,PC1表示第1个数,PC2表示第3个数(该数显示小数点),PC3表示第4个数;所以只有第三个数是特殊的;

参考原理图如下:

代码实现如下:

 1 void main()
 2 {
 3     //PB口做字形码的输出口,PC0~PC3控制数码管的轮流显示
 4     unsigned char i,j;
 5     static unsigned char LedNum[10] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
 6     //显示小数点要加0x80
 7     unsigned char CountNum[4] = {0,0,0,0}; //计数的百、十、个位和十分位
 8     DDRB = 0xFF; //设置B口为输出模式
 9     PORTB = 0xFF; //置高电平
10     DDRC = 0x0F;
11     PORTC = 0xFF;
12     
13     while(1)
14     {
15         i=0;  //用于PC0~PC3的轮转
16         for(j=0;j<10;j++) //依次显示4S"000.0"
17         {
18             i = ++i%4;
19             PORTC&=~(1<<i);
20             if(2==i) //如果是第三个数字则加个小数点显示
21                 PORTB = LedNum[0] + 0x80;
22             else //其余情况都没有小数点显示
23                 PORTB = LedNum[0];
24             delay_ms(10);
25             PORTC|= 0xFF;//复位,切换到下一个输出口
26         }
27         CountNum[0] = CountNum[1] = CountNum[2] = CountNum[3] = 0; //用于实现4位数的加一变化
28         while(1)
29         {
30             //计数加1,满9进位、后置0
31             if(CountNum[3]++==9)
32             {
33                 CountNum[3] = 0;
34                 if(9 == CountNum[2]++)
35                 {
36                     CountNum[2] = 0;
37                     if(9 == CountNum[1]++)
38                     {
39                         CountNum[1] = 0;
40                         CountNum[0]++;
41                     }
42                 }
43             }
44             //显示计数值
45             for(i=0;i<4;i++)
46             {
47                 PORTC&=~(1<<i);
48                 if(2 == i)
49                     PORTB = LedNum[CountNum[i]] + 0x80;
50                 else
51                     PORTB = LedNum[CountNum[i]];
52                 delay_ms(10);
53                 PORTC|=0xFF;
54             }
55             //999.9的时候退出
56             if(CountNum[0]==9 && CountNum[1]==9 && CountNum[2]==9 && CountNum[3]==9)
57                 break;
58         }
59     }
60 }

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏拭心的安卓进阶之路

@SuppressWarning 使用及支持的参数

@SuppressWarning @SuppressWarning 是一个注解,它的作用是抑制编译时的警告,可以用于标记整个类、某个方法、某个属性或者某个参数,...

1876
来自专栏用户画像

5.4.3微程序控制器

微程序控制器采用存储逻辑实现,也就是把微操作信号代码化,使每条机器指令转化成为一段微程序并存入一个专门的存储器(控制存储器)中。微操作控制信号由微指令产生。

581
来自专栏ACM算法日常

水果(STL+排序)- HDU 1263

夏天来了~~好开心啊,呵呵,好多好多水果~~ Joe经营着一个不大的水果店.他认为生存之道就是经营最受顾客欢迎的水果.现在他想要一份水果销售情况的明细表,这样J...

701
来自专栏大前端开发

【趣解编程】变量

如果把编程比作做菜的话,变量就是那些碗盆瓢勺,或装着原材料,或在做菜的过程中临时的摆放半成品,或装着最后的成品菜。

774
来自专栏FreeBuf

Gadget构造:从JIT-ROP到对抗XnR

写在前面 简单地说,本文介绍了两种构造gadget的思路。(在浏览器支持JIT的情况下) 演进:关于JIT-ROP 我们知道,最初DEP的出现是为了对抗的栈溢...

3054
来自专栏木东居士的专栏

程序员该如何管理后宫:朕只爱一个皇后!(单例模式)

1544
来自专栏程序员互动联盟

【答疑解惑】如何确定当前系统的大小端?

上周写过一个什么是大小端的答疑解惑,今天接着说如何使用C语言来确定当前系统的大小端,下面介绍两种有效的方法。 方法1:利用union类型 —— 可以利用unio...

1914
来自专栏林欣哲

科个普啦--内存的实现原理

本文讲内存的实现,从底层的二极管到内存的电路结构,本章逻辑线路为:电路-->二极管-->逻辑门-->组合逻辑单元j和存储单元-->内存 我们知道计算机本质是在做...

33110
来自专栏逸鹏说道

CPP--借助神器VS理解内存存储(含大小端对齐)

前面我发过用vs看内存存储的文章,90%耐心看的都懂了,这次结合上次发的文章,对大小端对齐再来个说明(有一个没学过计算机的朋友都懂了,自己看自己的悟性定位吧~努...

2966
来自专栏生信小驿站

数据处理第一节:选取列的基本到高级方法选取列列名

博客原文:https://suzan.rbind.io/2018/01/dplyr-tutorial-1/ 作者:Suzan Baert

672

扫码关注云+社区