原创 AVR IO

2011-6-28 23:34 1138 5 5 分类: MCU/ 嵌入式

硬件

 

对于第一项测试,在这里你只需要一些按钮和LED(发光二极管)灯用来连接AVRIO端口就可以了,PB0-PB5都是通过一个LED灯和一个1K的电阻连接到5V电源。电阻可以在LED的前面或者后面,并不影响电路的功能,但是必须需要一个电阻用来限流,否则容易烧毁LED

20110628232400001.jpg

如果LED连接的端口占用了ISP编程接口,当然这种情况并不常见,如果你遇到了这种情况,有一个办法就是试着在LED灯的电阻上再增加电阻,用来隔离。

 

PD0-PD3连接了四个按钮,每个按钮带有10K的上拉电阻:

 

20110628232400002.jpg

数字进制

 

关于数字进制,计算机中比较常用的几种进制有二进制、十进制、十六进制。比如在汇编语言中数字的二进制格式表示为0b00111010,十六进制表示为0x7F,你也可以转化成其他的数字,比如用windows自带的计算器。下面是一些例子:

Dezimal Decimal

Hexadezimal Hexadecimal

Bin?r Binary

0 0

0x00 0x00

0b00000000 0b00000000

1 1

0x01 0x01

0b00000001 0b00000001

2 2

0x02 0x02

0b00000010 0b00000010

3 3

0x03 0x03

0b00000011 0b00000011

4 4

0x04 0x04

0b00000100 0b00000100

5 5

0x05 0x05

0b00000101 0b00000101

6 6

0x06 0x06

0b00000110 0b00000110

7 7

0x07 0x07

0b00000111 0b00000111

8 8

0x08 0x08

0b00001000 0b00001000

9 9

0x09 0x09

0b00001001 0b00001001

10 10

0x0A 0x0A

0b00001010 0b00001010

11 11

0x0B 0x0B

0b00001011 0b00001011

12 12

0x0C 0x0C

0b00001100 0b00001100

13 13

0x0D 0x0D

0b00001101 0b00001101

14 14

0x0E 0x0E

0b00001110 0b00001110

15 15

0x0F 0x0F

0b00001111 0b00001111

100 100

0x64 0x64

0b01100100 0b01100100

255 255

0xFF 0xFF

0b11111111 0b11111111

"0b""0x"对于计算本身没有任何的意义,它们只是表示数字本身是2进制或者是16进制格式的。

有一点需要强调的就是二进制、十六进制或者十进制仅仅是对同一个数字不同的叫法而已。还有一点比较重要的是在计算机系统中都是从0开始的,比如有8个数字,那么第一个数字是0,第二个数字是1,最后一个数字7表示第8个数字。

 

汇编代码

 

我们第一个汇编程序,我们可以将它运行在我们的控制器上:

Include "m8def.inc"; 包含了完整的处理器定义的头文件

 

 ldi r16, 0xFF; 将固定值0xFF装入寄存器 r16

 out DDRB, r16; r16的内容输出到IO寄存器DDRB

 r16, 0b11111100, 0b11111100 载入r16

 out PORTB, r16;r16内容输出到IO寄存器 PORTB

 end: rjmp end, 无限循环 程序结束

 

汇编格式

 

写好的程序必须以". Asm"这种格式保存,例如:"leds.asm",但是我们并不能直接对控制器编程,首先我们需要一个编译器,例如我们使用wavrasm,打开一个工作窗口,将代码复制到里面,必须包含头文件"m8def.inc"( Atmel的汇编头文件),汇编器将命令代码编译成控制器可以理解的二进制代码,以"hex"文件格式输出,这个文件可以通过下载软件直接下载到控制器中。

Note:ATmega8的时钟配置

ATmega8默认使用内部1M的晶振,这对于许多应用来说比如UART是不足够精确的,一般我们使用4-MHz的晶振,但是需要对熔丝位进行调整,关于AVR的熔丝位的问题后面将具体介绍。

编译完成后输出文件"leds.hex"或者"leds.rom"等格式文件,然后使用PonyProg AVRISP或者其他的软件将文件下载到控制器的存储器。如果上面所有的事情都做好了,你将看到连接的两个LEDs被点亮了。

 ldi r16, 0xFF

0xFF(也等于0b11111111)直接装入寄存器r16中,AVR32个工作寄存器,r0-r31,它们通常作为IO寄存器(例如DDRB, PORTB, UDR ...)RAM之间的缓冲器。需要注意的是前面16个寄存器(r0-r15)并不能被每个汇编指令使用,也就是说有使用的限制;在控制中一个寄存器可以作为直接访问的内存单元,当然,控制器有很多的内存单元,但是它们仅仅是用来存储数据用的,为了操作这些数据,它们必须装入到一个寄存器中,只有这样才有可能去操作或者改变这个数据,

同样的还可以将寄存器比作是一个工作台,任何数据的编辑都必须在这个工作区内才可以进行。LDI指令将一个固定的值装入到工作寄存器中,在这种情况下这个值并不是来自存储空间,而是指令本身就知道这个数据。还有汇编指令并不是简单的一些字母的组合,而是包含了某些执行动作。例如ldiload immediate的缩写,load表示装载,immediate要说的就是这个值是由指令本省指定的。

分号后面的语句表示程序的注释,并不能被编译器认识。

下一条指令out,是将r16的内容送入到port B的方向寄存器,方向寄存器决定端口管脚是作为一个输出口和输入口用,如果寄存器中的一个位设置为0,那么相应的管脚就配置为输入,如果设置为1,那么管脚就就为输出,Der 3.

在这种情况下,Port B的六个管脚为输出,方向寄存器并不能像描述的那样可以直接访问,因此你必须通过寄存器r16-r31才能访问。

最后一条命令

ldi r16, 0b11111100

这条指令是将值0b11111100送入工作寄存器r16中,接下来的指令out PORTB, r16是将r16的内容送入到IO寄存器PORTB中,1表示在寄存器PORTB中控制相应的管脚输出5V0表示0V()

对于复制我装载指令(LDI, in, out ...)总是将第二个操作数复制到第一个:

     ldi r17, 15;  寄存器r17送入15

     mov r16, r17; 寄存器r17的内容送入r16

     out PORTB, r16; r16的内容送入IO 寄存器 "PORTB"

     in r16, PIND   寄存器PIND的内容送入寄存器r16

 

现在我没可以看到,前面两个LEDs被点亮了,因为端口管脚PB0PB10,因此电流从5V电源途经LEDs流入到了管脚;而其他四个LEDs没有被点亮,因为相应的管脚为1也就是5V

为什么前面的两个LED灯点亮了,被设置为0的最后两个位在哪里?因为数字的位是从右往左写的,最右边的为最低有效位LSB,被称为bit 0,最左边的为最高有效位MSB,bit 7"0b"这个前缀不是数字,是要告诉汇编器接下来的数字应该用二进制格式来解释。

bits.gif

LSB代表PB0PB7代表MSB,但是有的控制器型号的管脚并没有8个,例如AT90S4433的端口B只有PB0-PB5,那么多余的管脚其实就在内部,只是没有引出来而已。Das LSB steht für PB0, und das MSB für PB7...

 

输入

 

下面的程序实现的是port B输出和port D输入:

 . Include "m8def.inc"

         ldi r16, 0xFF

         out DDRB, r16; port B 所有的管脚设置为输出

         ldi r16, 0x00 ldi r16, 0x00

         out DDRD, r16;  port D 设置为输入

loop: loop:

         in r16, PIND; 读取port D的值(button)r16

         out PORTB, r16 r16的内容送到port B

         rjmp loop; 无限循环

port D连接了一个输入的设备,可以通过读PIND来得到IO端口的数据,使用IN这条指令将IO寄存器PIND的内容复制到r16,然后将r16的内容送到port B,

这种反复要和寄存器交换数据是必须的,因为没有哪条指令可以将一个寄存器复制到另一个IO端口。

 

rjmp loop

 

保证了指令in r16, PIND out PORTB, r16不断的重复执行,因此按键和LED灯的状态保持了一致。

注意:在这里没有提到单独的设置个别管脚的输出和输出的方法,在这里使用完整的端口纯粹是为了方便。

 

必要的延时

 

小心!在一些情况下可能发生你无法理解的现象,特别事在访问矩阵键盘的时候,下面就举个例子:

 

        ldi r16, 0x0F     

        out DDRD, r16;

        ldi r16, 0xFE ldi r16, 0xFE    

        out PORTD, r16;

        in r17, PIND;

上面的程序问题出在哪里?AVR是基于RISC的控制器,大多数一个时钟周期执行一条指令,PINXAVR内部总线之间有一个同步锁存器,使用该电路避免了当系统时钟变化的短时间内外部引脚电平也同时变化而造成的信号不稳定现象,因此需要产生一个约0.5-1.5个时钟周期的时延,具体可以参考手册。

该做什么?一般最简单的方法就是加一个nop指令,nop表示空操作。

        ldi r16, 0x0F

        out DDRD, r16;

        ldi r16, 0xFE ldi r16, 0xFE

        out PORTD, r16;

        NOP ; ;

        in r17, PIND;

未完待续..........

PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
我要评论
0
5
关闭 站长推荐上一条 /1 下一条