先打开上一篇文章新建的那个啥也不干的项目。想要单片机干点啥,你先要准备好 datasheet。
一般编程语言的第一个程序是打印 hello world,而单片机的第一个程序是点亮 LED。本质上是通过 IO 来控制芯片引脚电平的高低。
假如你的开发板有一排 LED,那么电路图一般长这样:
电源经过排阻再经过 LED 连接到芯片的引脚。假如,PC7输出的是低电平,则对应的L1灯点亮。
现在打开 ATmega16 的 datasheet 看一下引脚配置图:
ATmega16 的 IO 有4组:PA、PB、PC、PD,括号里面的是该引脚除了 IO 功能以外的其它功能。
每组IO都有3个寄存器:DDRx、PORTx、PINx(x对应A到D),4组IO一共是12个:
这三个寄存器是这么用的:
简单的说:DDR控制引脚是输入还是输出,1为输出,0为输入。
而当引脚被设置为输出的时候,PORT控制引脚输出的电平高低,1为高电平,0为低电平。
而PIN,则是输入的时候用来读取该引脚的电平。
我们再回到这个电路图,现在需要做的是把PC组的引脚设置为输出低电平。
可以这么用16进制赋值:
PORTC = 0x00;
DDRC = 0xFF;
项目模版含了头文件
#include <avr/io.h>
里面定义了AVR寄存器的名字。
整个程序长这样:
#include <avr/io.h>
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的时候,就是用这种与或非(&、|、~)的组合。
最后放一下我的公众号二维码
网友评论