带你AVR入个门:1.点亮LED(上)

先打开上一篇文章新建的那个啥也不干的项目。想要单片机干点啥,你先要准备好 datasheet。

一般编程语言的第一个程序是打印 hello world,而单片机的第一个程序是点亮 LED。本质上是通过 IO 来控制芯片引脚电平的高低。

假如你的开发板有一排 LED,那么电路图一般长这样:


带你AVR入个门:1.点亮LED(上)_第1张图片

电源经过排阻再经过 LED 连接到芯片的引脚。假如,PC7输出的是低电平,则对应的L1灯点亮。

现在打开 ATmega16 的 datasheet 看一下引脚配置图:


带你AVR入个门:1.点亮LED(上)_第2张图片

ATmega16 的 IO 有4组:PA、PB、PC、PD,括号里面的是该引脚除了 IO 功能以外的其它功能。

每组IO都有3个寄存器:DDRx、PORTx、PINx(x对应A到D),4组IO一共是12个:


带你AVR入个门:1.点亮LED(上)_第3张图片

带你AVR入个门:1.点亮LED(上)_第4张图片

带你AVR入个门:1.点亮LED(上)_第5张图片

带你AVR入个门:1.点亮LED(上)_第6张图片

这三个寄存器是这么用的:


带你AVR入个门:1.点亮LED(上)_第7张图片

简单的说:DDR控制引脚是输入还是输出,1为输出,0为输入。

而当引脚被设置为输出的时候,PORT控制引脚输出的电平高低,1为高电平,0为低电平。

而PIN,则是输入的时候用来读取该引脚的电平。

我们再回到这个电路图,现在需要做的是把PC组的引脚设置为输出低电平。

带你AVR入个门:1.点亮LED(上)_第8张图片

可以这么用16进制赋值:

PORTC = 0x00;
DDRC = 0xFF;

项目模版含了头文件

#include 

里面定义了AVR寄存器的名字。

整个程序长这样:

#include 


int main(void)
{
    PORTC = 0x00;
    DDRC = 0xFF;
    while (1) 
    {
    }
    return 0;
}

编译、下载、开发板重新上电……bling~,8盏LED点亮了。

PORTC = 0x00;
DDRC = 0xFF;

换成2进制的写法等价为:

PORTC = 0b00000000;
DDRC = 0b11111111;

8位,每个引脚对应1位。

假如你想要只点亮其中1盏LED呢?比如只点亮L3?

L3对应的引脚为PC5,你可以这么写:

PORTC = 0b11011111;
DDRC = 0b11111111;

当然你也可以写成16进制:

PORTC = 0xDF;
DDRC = 0xFF;

但是这种编程风格对阅读代码毫无帮助。

一种比较优雅的写法是:

PORTC =  ~(1 << PC5);

PC5在avr/io.h里定义好了,其实就是5。

上面那句代码翻译过来就是:

PORTC =  ~(1 << 5);

再翻译:

PORTC = ~(0b00000001 << 5);

再翻译:

PORTC = ~0b00100000 ;

再翻译:

PORTC = 0b11011111 ;

看,又变回来了。

这么直接赋值有一个问题,除了PC5对应的L3点亮以外,所有LED都灭了。

假如我事先不知道其它LED的亮灭情况,在不影响其它LED的情况下单独点亮L3呢?

优雅的写法是:

PORTC &=  ~(1 << PC5);

相当于:

PORTC =  PORTC & 0b11011111;

无论PORTC寄存器原来的值是什么(想象为0b????????),跟0b11011111进行&运算之后,就变成了0b??0?????,无论其它位是什么,反正PC5是0。

假如我要同时点亮PC3跟PC5呢?这么写:

PORTC &=  ~((1 << PC5)|(1 << PC3));

0b00100000跟0b00001000进行|运算后变成了0b00101000,剩下的自己推导。

假如我要同时熄灭PC3跟PC5呢?这么写:

PORTC |= (1 << PC5)|(1 << PC3);

恭喜你已经上道了,请记住上面两种写法,当要单独将某个寄存器的某一位置0或者置1的时候,就是用这种与或非(&、|、~)的组合。

最后放一下我的公众号二维码

带你AVR入个门:1.点亮LED(上)_第9张图片

你可能感兴趣的:(带你AVR入个门:1.点亮LED(上))