首页 > 安全资讯 >

M8系统开发手记(3)

11-01-17

Wayne Huang 2011年01月15日 0  写在前面的话话说,这篇文章更新的有些晚,主要是感觉这周事情比较多的缘故吧。感觉很多时候,我们只是在忙忙碌碌,却不知道自己人生所追求的到底是什么,我们追求的是否会朽坏?很多时候我们并不会

Wayne Huang
2011年01月15日
0  写在前面的话
话说,这篇文章更新的有些晚,主要是感觉这周事情比较多的缘故吧。感觉很多时候,我们只是在忙忙碌碌,却不知道自己人生所追求的到底是什么,我们追求的是否会朽坏?很多时候我们并不会去思考这种问题,也会在这事上感到迷茫,感觉没有这种觉悟,作人就好似机器一样,每天重复着类似的事情,一个倒下了,还会有很多很多的替代。在每天的忙忙碌碌中,我们到底寻求的是什么?是这个我们终将离开的世界上的东西么?在缺失这种觉悟的情况下,人渐渐的失去了他所特有的属性,成为了庞大流水线上的一台机器,而他的荣耀就是他的生产效率~

1  M8系统中的Hello World
在上面一章中,我们已经了解到了M8最小系统的构建。接下来,我们将在上一个系统的基础上,尝试我们第一个小作品-流水灯。对于有编程基础的同学来说,"Hello World"一般都是我们写的第一个程序,而在MCU的世界里,流水灯往往也是第一个入门的经典实现。如同"Hello World"程序一样,流水灯的思想也是实现基本的输出操作。不同之处在于,流水灯的输出是动态的。所实现的效果,就如同霓虹灯的闪烁效果,根据喜好和实际的硬件,可以选择不同的效果。那么,接下来我们就分析一下流水灯的实现吧。

1.1  M8流水灯实现的介绍与分析
我们这里所要实现的流水灯,实际上就是一个个依次闪烁的灯而已。但是,既然这通常作为MCU的入门实现,自然有其道理,一方面他能让你熟悉MCU各IO口的操作,其次也让你熟悉MCU相关的一些功能。这样看来,MCU作为入门,是再好不过的实现了。话说,MCU的中文意思是"微控制器",那么作为IO口的操作,自然是一个重点,这和我们这种搞软件的CPU-"中央处理器"还是有比较大的不同的,因为CPU强调的是运算性能,而MCU强调的是控制性能。所以,你搞MCU如果连基本的IO口操作都不会,那你真的出门连招呼都不好意思和别人打了。不过,感觉现在这种本应该是"羞耻"的事情,却越来越成为"主流"文化了。比如,某些非私有单位的信息化建设负责人,往往可能连什么是信息化系统都不知道。更有甚者是除了windows就不知道其他操作系统,除了SQL Server,就不知道其他的数据库,感觉在他们的眼里,我们就成了"非主流"。言归正传,因为IO口的操作是MCU的生命,所以MCU的学习自然也要从IO口开始,就好像你写了个没有输出的理财程序,然后别人运行了很多次却没有任何提示,难道你和用户说:程序他自己知道结果的,你不用为你的投资操心?那这个程序不成了"浮云"了?

1.1.1  M8系统的IO口操作介绍
如上所述,IO口的操作是相当重要的,所以我们这里开篇就来看看这个灵魂人物-IO口。我个人觉得,衡量IO口能力的参数主要是IO口的驱动力。对于M8来说,输出输入电流是对等的,每个引脚最大不能超过40mA,但是总电流不能超过300mA。当然,针对每个特定的IO口,还是有一些更细化的限制,但是不论是输入还是输出电流,驱动发光二极管还是可以的。为什么这里我要分开说输入和输出呢?因为在51中,比如AT89S52系列,他的输入电流和输出电流是不对称的,因此在为51选择类似七段数码管的时候,会考虑到共阴公阳的问题。同时,M8与常规51比较大的特点是能够启用内置的上拉电阻。

M8控制IO口的寄存器有三个,分别是方向寄存器"DDRx"、数据寄存器"PORTx"和引脚寄存器"PINx"。"DDRx"和"PORTx"是可读写寄存器,"PINx"是只读寄存器。名字里的"x"是只具体的端口字母。而控制寄存器里的"SFIOR"里的"PUD"位是控制传说中的上拉电阻的。表1就是他们不同组合的结果。"PINx",不论是引脚作为输入还是输出,其值总为引脚上实际的电平值。


 
DDRx  PORTx  PDU  I/O  上拉  说明 
0  0  X  输入  无效  高阻 
0  1  0  输入  有效  外部引脚拉低时输出电流 
0  1  1  输入  无效  高阻 
1  0  X  输出  无效  低电平推挽输出,吸收电流 
1  1  X  输出  无效  高电平推挽输出,输出电流 
 

Table 1: IO相关寄存器定义

话说,我一般用IO口的时候,设计的驱动电流一般为5mA左右。因为虽然说手册的最大值离我使用的电流还有一大截。但是考虑到各个引脚的诸多限制,以及总电流不超过300mA的这种限制,采用平均5mA的的设计,可以避免因为一时的疏忽,比如接了8个40mA的负载,那这样虽然感觉每个引脚都没超过限定,但是总体就超额了。

1.1.2  M8系统的定时器/计数器0操作介绍
在前面我们已经介绍了IO口,接下来我们要介绍实现流水灯所必须的另一个部件-定时器。根据我们刚才的介绍,流水灯是依次的让LED闪烁。但是,即使MCU执行速度多慢,总比我们人的反应速度快,甚至是超越人的反应速度。因此,我们需要一个定时器来让我们每一次的变换可以延迟一段时间,让我们迟钝的视觉能有一个反应的时间。话说,人有时候的确是很迟钝的,所以相对来说由人组合起来的集团的反应有时候会更迟钝,一边已经火烧眉毛了,一边却还在那边考虑考虑、研究研究、商量商量、调查调查,到最后往往结果已经并不重要了,无谓的浪费着人力物力财力。

在M8中有3个定时计数器,我们这次用的是功能最弱的定时计数器0。在M8中,定时器的时钟频率是基于他的系统时钟的,我们可以直接使用系统时钟,也可以使用对系统时钟分频后的时钟信号。当然也能使用M8的T0引脚上的信号作为时钟信号,这个时候他就成了一个计数器。M8定时计数器0的时钟选择,是通过TCCR0进行的,对其中CS02、CS01和CS00的设置不同,就能选择不同的时钟信号源。表2就是时钟信号选择的相关定义。


 
CS02  CS01  CS00  说明 
0  0  0  无时钟源 (停止定时计数器0) 
0  0  1  CLKI/O (系统时钟)
0  1  0  CLKI/0 ÷8 (来自分频器) 
0  1  1  CLKI/O ÷64 (来自分频器) 
1  0  0  CLKI/O ÷256 (来自分频器) 
1  0  1  CLKI/O ÷1024 (来自分频器) 
1  1  0  外部引脚T0的下降沿 
1  1  1  外部引脚T0的上升沿 
 

Table 2: 定时器时钟设置定义

除了刚才说的TCCR0,M8的还有一个叫作TCNT0的寄存器。这是一个8位寄存器,在没次时钟信号到达的时候,这个寄存器都会自加1,由于这个过程是独立于系统运算总线的,所以这个过程并不会被被系统指令所打断。当这个计数器达到最大值0xFF后,将发生一个溢出事件,并置零TCNT0寄存器。在发生TCNT0溢出的时候,TIFR寄存器中的TOV0位会被置"1"。同时,如果你设置了TIMSK寄存器中的TOIE0位,还会发生一次系统中断,不过中断我们这里还没涉及到,所以就不详细描述了。由于是TCNT0是累加的,所以在设置寄存器的时候,要稍微注意一下。

1.2  M8流水灯实现的电路图
既然名字叫流水灯,自然要有灯才可以。说到这里想起了那个"鱼翅粥",话说有个人看到一家店的招牌叫"鱼翅粥店",于是乎进入店堂点了份"鱼翅粥",但是吃了半天都没吃到传说中的鱼翅,随即叫来小二问其缘由,小二答到:"这本来就没鱼翅啊,不过我们老板名叫鱼翅。"言归正传,我们这里用的"灯"其实就是发光二极管,又名LED,他有二极管的单向导通性。在我的电路图中,我一般用1kΩ电阻作为限流电阻。这样,既能保证最大电流不超过额定值,也能保证LED正常发光。当然,由于LED有方向性,所以安装前最好先判断一下极性。

由于Blog的限制问题,以及考虑到电路图的清晰程度。我电路图的格式是采用的PDF,不能上传到Blog。因此,如果你有需要可以在下方留下你的邮箱地址,我会第一时间发送给你。

1.3  M8流水灯程序的实现
如果用生物来形容计算机的话,硬件就相当于肉体,软件就好像精神。而作为MCU系统的开发者,我们往往既需要铸造肉体,也要成就精神。我们赋予他们肉体作为载体,同时又赋予他们灵魂和生命。当然,我们所做的远没有神创造世界那么伟大。但是,创造的乐趣只有你亲身经历才能体会的。但是MCU系统的软件设计和普通PC的软件设计有些不同,因为他是基于硬件的,有时候硬件结构的不同将会同时影响到软件的实现。所以,一般正常的思路是现确定硬件,再根据硬件写软件。

1.3.1  程序的实现
我们这里是让4个LED按照从低位到高位依次亮起,在达到最高位后全部熄灭,并从头开始。这里,我们采用定时器0的忙等方式实现延迟的效果。下面是我实现的程序。根据程序设定,每个LED大约延迟250ms左右。

#include <avr/io.h>

// 1024 * 0xF4 / 1000000 = 250ms
#define T0CNT 0xFF - 0xF4

int main() {
  unsigned char i = 0;
  //设置D口为输出口
  DDRD = 0xFF;
  PORTD = 0;
  //设置分频为1024,延迟25ms
  TCNT0 = T0CNT;
  TCCR0 = _BV(CS02) | _BV(CS00);
 
  do {
    //等待计时器溢出
    while(!(TIFR & _BV(TOV0)));
    //将D口相应的引脚置1
    PORTD = _BV(i);
   
    i += 1;
    if (i == 4) i = 0;
    //重置定时器
    TCNT0 = T0CNT;
    //清除溢出标志
    TIFR |= _BV(TOV0);
  }while(1);
  return 0;
}

1.3.

相关文章
最新文章
热点推荐