适用于刚从Arduino语言转到AVR C语言编程的人。
目标
理解AVR架构下GPIO的使用,即输入和输出。理解寄存器赋值和Arduino语言赋值的区别。实现用开关控制LED的点亮和熄灭。
使用到的硬件
- Arduino uno
- 面包板
- 杜邦线
- 电阻
- 开关
- LED
- 三极管
完整代码
1 |
|
电路图
需要注意的是,这里关于LED部分使用了一个三极管开关电路。如果你没有三极管,可以直接将Pin和LED及电阻连接。Arduino有足够的电流供给,该电路适用于弱电流GPIO的开发板,比如Jetson Nano。
嵌入式的“Hello World”,点亮一盏LED
在Arduino语言中,只需要给某个Pin赋值就可以点亮一盏LED。用C语言编程与Arduino语言编程过程一致。首先看一下如何用Arduino语言点亮一盏灯:
1 |
|
该程序可以在Arduino IDE的示例Blink中查看,其功能是小灯按两秒一个周期的频率闪烁。那么点亮一盏LED需要做什么?首先在“setup()”里初始化Pin的方向(输入or输出),然后给该Pin赋值0(熄灭)/1(点亮)。
那么同样的,使用C语言编程,也要完成这两步。Atmega328P的每个Pin有三个寄存器,可以通过配置这些寄存器来实现一些GPIO的功能。这里需要用到两个寄存器:DDxn和PORTxn。除了这两个寄存器,每个Pin还有一个Pinxn寄存器。关于这三个寄存器的详细描述,请参考文献:数据手册的P59。
如果你学过51单片机,那么应该对寄存器很了解了。在Atmega上的使用方法上没有区别。如果你并不了解寄存器,关于寄存器,它可以理解为一个开关,一般它的每一位有两个值0/1。假设我们的动作器是个灯,那么我可以通过操控这个开关来改变动作器的状态(点亮or熄灭)。以方向寄存器DDxn为例,这里的“x”代表着你所使用的Pin的Port位置。“n”代表着Pin在该Port上的位置。你可以很容易的在网络上找到Arduino的引脚图,比如下图是在Arduino官网截的:
回到点亮一盏LED上,首先需要配置Pin的方向,需要用到寄存器DDxn。当该寄存器为0时,Pin被配置成输入,1则是输出。从上图中可以很容易的找到你所使用的Pin的寄存器位置。以D3为例,它的寄存器位置为PD3。将该Pin配置为输出:
1 | DDRD = 1 << DDD3; |
DDRD(可以理解为Digital Direction D)是整个PortD的方向寄存器,DDD3是Pin-D3的方向寄存器。通常来说我们配置一个Pin的方向,并不希望改变其它Pin的方向。所以需要通过移位运算来给DDD3赋值。当然你也可以用字节操作的方式来给DDD3赋值:
1 | DDRD |= 0x04; |
现在进行第二步,对Pin的高-低电平状态赋值。将该Pin配置为高电平:
1 | PORTD = 1 << PD3; |
最后,来实现一下与示例Blink一样的功能:
1 |
|
注意:在库<util/delay.h>中,delay函数为“_delay_ms(num); ” “ _delay_ns(num); ”两种。使用Arduino语言中的“delay(num)”无法实现延时功能。
使用开关控制LED
对于配置一个Pin的功能,在手册P60可以找到下图这张表格:
不同于点亮一盏LED,对于按键,需要将方向配置为输入:
1 | DDRC = 0 << DDC0; |
读取按键的状态需要用到寄存器“PINx”。该寄存器会返回所选Port的电平状态,高为1,低为0。我们需要对按键的状态进行判断:
1 | if ( PINC & (1 << PINC0) ) {} |
需要注意的是,PINx寄存器是整个PortC的Pin-Status寄存器。需要通过移位指令和逻辑运算来提取所选Pin的电平状态。
对移位指令的简化
在AVR编程中,位移指令和逻辑运算会经常用到,不熟悉的话可以在程序顶端添加下列程序对位移指令和逻辑运算进行封装:
1 |
然后通过下列方法来配置Pin:
1 |
|