树莓派学习(四) — 初识GPIO

树莓派基本环境搭建好之后就可以在上面安装各种应用了。这样对树莓派来说就是一台小型电脑,基本电脑、服务器能做的事情她也都能做。只不过内存比较小。1G的内存多跑2个服务内存就吃不消了。最近VPS也是,apache和tomcat一跑,差不多吃了500M内存,也不知道能怎么优化了。而且家里光猫无法破解成桥接模式,外网穿透还没有做,所以就没装什么服务。 所以还是玩一玩树莓派相比电脑特有的东西吧。

 

一 GPIO

 

GPIO全称是General Purpose Input Output (通用输入/输出)。广义上来说它并不是类似USB、DVI、HDMI这样一种特定协议的接口,而是通用接口的总称。对于了解单片机的人来说应该非常熟悉,而对于我们这些纯软件开发,没有接触过单片机的人来说,并不是很好弄清楚到底是个什么东西。 下面是维基百科的定义:

general-purpose input/output (GPIO) is an uncommitted digital signal pin on an integrated circuit or electronic circuit board whose behavior—including whether it acts an input or output—is controllable by the user at run time.

从上面总结出几点:

    1. 数字针脚(可以是集成电路上的针脚,比如CPU或位处理器的针脚;也可以是开发板比如树莓派、Arduino上提供的GIPO接口)
    2. 可以用来输入或输出
    3. 运行时可控

在树莓派上,CPU上方的40个PIN脚就是GPIO接口。通过GPIO接口可以连接很多外部设备,并且和设备进行通信,并且可以通过代码进行控制。这也就是树莓派相比电脑最大的优势。大家可能会说电脑也可以通过USB连接很多外部设备。USB其实也是一种通用的串行接口,就本质上来说没有太大区别。不过USB设备需要设备厂商提供驱动,目前在单片机或树莓派上外设基本都是通过GPIO连接,当然USB的也可以搜索到,但是非常少。

所以从淘宝买了一些外设回来。这些外设通过跳线和GPIO接口连接并通信。所以有了GPIO和外设就能做很多有意思的事情,这就逐渐和物联网、智能设备关联起来了。

 

 

二 树莓派GPIO功能

 

树莓派提供了40个PIN口,大致的定义如下。其中黄色的GPIO PIN口有26个。 其余的是供电接口和接地口。每个GPIO接口都可以用作输入和输出,可以根据需要进行使用。 每个GPIO针脚都有编号,但是编号不是线性的。 

GPIO layout

 

针脚电压

 

2个红色的针口提供了5V的输出电压,2个橘色的针口提供3.3V输出电压。 而8个黑色针口作为接地为0V。 其他的接口可以提供输入输出功能。所以作输出时可以提供3.3V电压,而作为输入时能承受3.3V电压。

 

输入/输出

 

对于GPIO接口来说,最重要的就是输入和输出功能。对于计算机来说能识别的只有0和1,而对于数字电路来说通过高低电平来表示输出的值是0还是1。 因为树莓派GPIO接口的电压是3.3V。所以用3.3V表示高电平,也就是1,而用0V表示低电平,也就是0。一般来说高低电平会是一个电压范围。

在买外设的时候发现,有些设备是5V高电平,有些是3.3V。了解到单片机分为3.3V和5V,就是指的是GPIO接口的电压。如果用GPIO口产生一个3.3V的高电平给一个5V的设备,可能会被认为是低电平,如果直接接上一个5V设备作为输入电平,可能会导致树莓派烧坏。所以使用外设是要注意这个外设的电压是5V还是3.3V。

当GPIO用做输入时,会有高电平、低电平、高阻态三个状态。高阻态,指的是电路的一种输出状态,既不是高电平也不是低电平,这个时候因为状态不确定,读取GPIO时可能会导致数据不正确。所以引入了上拉和下拉电阻的概念。

  • 上拉电阻:电阻一端接VCC,一端接逻辑电平接入引脚(如单片机引脚)
  • 下拉电阻:电阻一端接GND,一端接逻辑电平接入引脚(如单片机引脚)

A GPIO pin designated as an input pin can be read as high (3V3) or low (0V). This is made easier with the use of internal pull-up or pull-down resistors. Pins GPIO2 and GPIO3 have fixed pull-up resistors, but for other pins this can be configured in software.

从官网文档看,树莓派每个GPIO接口都有上拉和下拉电阻,大部分是可以通过软件的方式来设置。所以树莓派在连接外设时可以不需要外部的上拉或下拉电阻。而是初始化时设置一下就可以了。

 

更多上下拉电阻的概念可以参考:

Pull-up and pull-down Resistors

上拉电阻与下拉电阻有什么作用

GPIO电路图以及上拉电阻的作用

上拉电阻和下拉电阻

电阻(4)之上拉电阻与下拉电阻详解

 

 

GPIO数据传输

 

和外设之间进行通信主要通过GOIP接口进行输入和输出。GPIO接口提供了可编程的方式从外设读取或向外设发送状态数据。通过控制控制高低电平可以输出不同状态,所以用做开关控制是非常简单有效的。作为输入时可以读取数值或者是状态。虽然每次只能传递0和1这样的数据,但是多次连续传递0和1组合起来,就可以传递复杂的数据。 比如温度传感器就是用GPIO口通过单总线协议进行数据交换。 通过GPIO可以进行各种协议的通信。

 

 

PWM

 

脉冲宽度调制(PWM),是英文“Pulse Width Modulation” 的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。比如伺服电机使用输入PWM信号的脉冲宽度来确定它们的旋转角度,LCD显示器基于PWM信号的平均值来控制它们的亮度。PWM是根据频率占空比进行震荡的数字信号(方波)

  • 周期(Period):脉冲信号从一个上升沿到下一个上升沿的时间
  • 频率(Frequency):描述1秒钟内脉冲周期发生的次数
  • 占空比(Duty): 一个周期中高电平时间占整个周期时间的百分比

所以我们可以通过设置不同的占空比来控制脉冲宽度或者信号的平均值 ,从而达到控制外部设备的目的。下图是占空比为0,25%和100%。

 

PWM有硬件和软件两种输出方式:

  • 软件方法:将普通GPIO引脚作为PWM输出引脚,依据实际需求,配置好计时器,在指定计时周期翻转GPIO引脚电平,实现PWM功能。通过终端方式实现,占用CPU,精度差,控制复杂
  • 硬件方法:在原理图设计时,将支持硬件PWM的引脚作为PWM输出引脚,这样就可以直接通过配置寄存器,采用硬件实现PWM功能了。通过事件方式实现,不占用CPU。

 

在树莓派上,所有GPIO接口都支持软件方式输出PWMi信号,GPIO12、GPIO13、GPIO18、GPIO19支持硬件方式输出信号。

PWM (pulse-width modulation)

  • Software PWM available on all pins
  • Hardware PWM available on GPIO12, GPIO13, GPIO18, GPIO19

 

 

I2C

 

I2C (Inter-Integrated Circuit)是一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。主要用来和一些简单的外设进行小数据的传输。一般连接一些传感器或驱动器,比如速度传感器、温度传感器、LCD显示器或电动马达。

I2C是一个同步串行(synchronous serial )接口。使用3条总线进行连接。分为主从设备,主从设备可以进行通信。

  • Shared clock signal (SCL): 因为I2C是串行的,所以需要SCL时钟总线发出信号来同步主从设备间的数据传输,发出时钟信号的设备为Master,而连接的设备为Slave。
  • Shared data line (SDA):主从设备间通过SDA总线进行数据传输,因为只有一根数据线,所以I2C接口是半双工
  • Common ground reference (GND) : 接地

 

I2C接口支持多个设备连接在同一个总线上。每个设备都有自己的地址,但是这个地址是软件实现的,这个地址用7bit表示,所以理论上I2C最多可以连接128个设备。而因为通信协议中带上了设备地址, 所以同时只能和一个设备通信,设备只会响应传递给自己地址的数据。I2C接口传输速率有Master的时钟频率决定。树莓派上默认是100KHZ。这个需要根据外设来进行设置。

 

树莓派上提供了2组I2C接口,分别是GPIO2、GPIO3和GPIO0、GPIO1。但是GPIO0和GPIO1是作为EEPROM,这个主要是用来刷主板上的ROM,所以不用来和外设进行通信。

I2C

  • Data: (GPIO2); Clock (GPIO3)
  • EEPROM Data: (GPIO0); EEPROM Clock (GPIO1)

 

SPI

 

SPI是串行外设接口(Serial Peripheral Interface)的缩写。是一种高速的,全双工,同步的通信总线。SPI有比较高的传输速度,所以适合用看来进行带宽要求比较高的数据传输,比如图像的传输。很多传感器都支持SPI和I2C接口。

SPI是一个同步串行(synchronous serial )接口。使用5条总线进行连接。分为主从设备,主从设备可以进行通信。

  • Master Out Slave In (MOSI)  数据总线,用于master向slave发送数据
  • Master In Slave Out (MISO) 数据总线,用于master接收slave发来的数据
  • Shared clock signal (CLK) 时钟总线,因为SPI是一个同步接口,所以也需要时钟总线来同步传输
  • Common ground reference (GND) 接地
  • CS 片选信号线,如果只有一个设备不需要

因为SPI使用了两条数据线传输数据,所以是全双工模式。 SPI同样支持多个设别连接,但是和I2C不同的是,SPI Slave设备地址是用硬件方式实现的,所以每个设备有一个CS总线和Master设备想了。这样Master可以同时和多个Slave设备进行通信。 SPI的传输熟虑也是由Master是时钟触发频率决定的,一般为16MHz 到 25MHz, 相比I2C要快了很多。

 

树莓派提供了2组SPI接口,其中SPI0提供了2个片选接口,SPI1提供了3个片选接口,所以树莓派最多应该可以连接5个SPI设备

SPI

  • SPI0: MOSI (GPIO10); MISO (GPIO9); SCLK (GPIO11); CE0 (GPIO8), CE1 (GPIO7)
  • SPI1: MOSI (GPIO20); MISO (GPIO19); SCLK (GPIO21); CE0 (GPIO18); CE1 (GPIO17); CE2 (GPIO16)

 

UART

 

通用异步收发传输器( Universal Asynchronous Receiver Transmitter), 一般也简称为串口(serial ports)。我们在使用电脑时也经常通说串口,比如老的COM口、现在的USB口。所以大家说串口一般是一个总称,而单片机上一般书串口指的是UART,它使用TTL电平(2.6V-5V为1,0为0-0.4V),而电脑上的COM口使用RS232电平 (逻辑1=-3V~-15V逻辑0=+3~+15V) 。COMTTLRS232RS485

UART接口是异步接口,所以不需要时钟总线。设备之间的传输速率是可以协商的,所以是通用接口。 设备直接通过RX和TX两个数据总线连接,所以是全双工模式。UART接收到的数据会放到一个FIFO队列中,直到设备读取。UART支持3线和5线两种连接方式:

  • 3-Wire ports include data receive (RX), data transmit (TX), and ground reference (GND) signals.
  • 5-Wire ports add request to send (RTS) and clear to send (CTS) signals used for hardware flow control. Flow control allows the receiving device to indicate that its FIFO buffer is temporarily full and the transmitting device should wait before sending any more data.

5线相比3线多了RTS和CTS两条控制总线。作用就是当FIFO队列满了的时候通知设备暂时不要传递数据。和I2C以及SPI不同的是,UART是点对点通信。速度比I2C块,但是可能要比SPI慢。

 

树莓派在GPIO14和GPIO15 接口提供了UART功能,但是没有提供RTS和CTS总线,所以只能以3线的方式连接。

  • Serial
    • TX (GPIO14); RX (GPIO15)

 

小结

 

之前对GPIO的理解有点偏差,认为普通GPIO口只能发送0,1作为开关,而其他I2C,SPI等接口可以发送负责的数据。 后来发现一些外设就是通过普通GPIO口来进行了单总线通信。 所以GPIO确实只能产生高低电平,但是如果连续产生,就可以连续的发送数据给设备。 而I2C, SPI这些总线本质也都是发送电平,不同的是他们是按指定的协议方式发送。 而实际外设中有很多是自定义协议,所以理论上无论是自定义协议,还是标准的I2C,SPI这些协议,都是可以用任意的GPIO口来通信的。

只是PI上定义了I2C, SPI这样的接口,如果不使用这些接口,而用普通GPIO接口就需要自己来实现这个协议,而不能使用已有的I2C,SPI的库来通信。对于自定义协议也是一样。其实设备的针脚是可以任意GPIO的、只是如果使用了已有的库文件,需要修改对应的针脚设置。所以GPIO功能是非常强大的,你可以使用GPIO来和任意协议的设备通信,可以用GPIO来模拟USB通信,设置你可以模拟硬盘IDE接口和硬盘通信。当然在速度上可能会有限制。

 

 

三 GPIO和外设

 

一般来说只需要几根杜邦线和外设就可以直接和树莓派的GPIO接口连接了,但是树莓派装到盒子里,每次直接插拔线很不方便,所以买了一个GPIO扩展板和排线,这样就可以在外部来连接GPIO口了。扩展板上摆标明了针脚的定义,配合面包板可以很方便的连接外设。

选了几个有代表性的外设

  • 诺基亚5110蓝屏:最上面这个,提供了8个针脚。
  • 蜂鸣器: 左上这个是无源蜂鸣器,它通过一条I/O线进行通信。用2K~5K的方波去驱动可以放出不同的声音。所以这个设备可以是同具有PWM功能的GPIO接口去控制。 而如果是一个有源蜂鸣器,用普通GPIO接口给高电平就可以发生,因为自带了震荡源。
  • 温度湿度传感器:左下是DHT22传感器,也是所以买的外设中最贵的。它只有3个针脚,一个用来接收传感器的温度和湿度的信息。正常GPIO口只能传递0和1,所以他是利用了单总线协议来传输数据给树莓派。(这个传感器也有4针版本,具体连接参考设备资料)
  • 4位数码管: 右边这个是数码管的背面,正面是8.8.8.8的4为数码管。它有4个针脚,其中包括了CLK和DIO就是时钟总线和数据总线。

有了这些外设,就可以进行设备连接。下面这个图是树莓派官网的一个交通信号灯的连接图。3个信号灯并联,上方是一个有源蜂鸣器,最下面是一个开关。通过连接的GPIO口进行编程,就能控制信号灯和交通等一样亮、闪烁、切换,蜂鸣器发出声音。并且可以根据读取开关状态来做一些控制操作。

GPIO diagram

 

 

 

四 GPIO编程

 

前面都是一些和硬件相关的基本支持,对于码农来说的主要还是通过编程来实现各种设备的共功能。和硬件交互我们听到最多的就是驱动程序。下面是维基百科对设备驱动解释:

In computing, a device driver is a computer program that operates or controls a particular type of device that is attached to a computer.[1] A driver provides a software interface to hardware devices, enabling operating systems and other computer programs to access hardware functions without needing to know precise details about the hardware being used.

A driver communicates with the device through the computer bus or communications subsystem to which the hardware connects. When a calling program invokes a routine in the driver, the driver issues commands to the device. Once the device sends data back to the driver, the driver may invoke routines in the original calling program. Drivers are hardware dependent and operating-system-specific. They usually provide the interrupt handling required for any necessary asynchronous time-dependent hardware interface.[2]

 

简单说驱动也是一个程序,通过总线来和设备来进行通信和交换数据, 这个程序和硬件还有系统相关,所以编写一个驱动程序需要了解这个设备。比如当一个LED灯亮起来

from gpiozero import LED
from time import sleep

led = LED(17)

while True:
    led.on()
    sleep(1)
    led.off()
    sleep(1)

这段python代码很简单,使用了gpiozero库中的LED。 他已经为我们封装好了和LED硬件交互的一切。所以这个库我们也可以称为是LED的驱动程序。从前面我们知道要让一个LED灯亮起来,给GPIO针脚一个高电平输出就可以了。对应到程序就是设置一个1。 而对于驱动程序来说他,他需要知道把这个值存到那个寄存器,发什么指令给芯片。对芯片来说收到了指令,有了数据,就可以执行对应的操作了,比如给一个高电平。这个其实就是数字电路的基础了。(参考:代码是如何控制硬件的

所以对于我们使用树莓派来和外设进行通信主要有两种方式: 使用已有的库(驱动)或者自己撸驱动。对于我们来说撸驱动这个实在是有点难,所以主要还是使用现有的一些库。当然现有的库也有很多种,有的封装的非常彻底,比如上面那个LED,只用无脑的调用。有的只是封装的了基本通信,而数据的解析需要自己来做。所以根据需要选择合适的库吧。

 

Scratch

树莓派支持使用scratch进行编程,这个是针对小朋友设计的。可以不认识英文单词,也可以不会使用键盘。构成程序的命令和参数通过积木形状的模块来实现。用鼠标拖动模块到程序编辑栏就可以了。在Raspbain的桌面版系统中有这个工具

操作是这种图形化的方式编程,拖拽组合成一定逻辑功能程序。 (说到拖拽编程突然想起了以前用.NET C#做WinForm程序,哈哈)

 

 

GPIO with Python

 

Python确实是个很神奇的东西吗,从运维、到大数据,再到人工智能和物联网,感觉是无所不能。通过gpiozero这个库可以很方便的操作GPIO接口。具体可以参考他们的文档。除了官网推荐的这个库之外看到还有很多实用RPi.GPIO库的。除此之外还有其他一些库,可以自己搜索一下了解一下区别,目前而言PRI.GPIO是使用比较多的,不过好像目前还是不支持I2C,SPI。  Compare and contrast Python GPIO APIs

 

 

wiringPi

 

WiringPi is a PIN based GPIO access library written in C for the BCM2835, BCM2836 and BCM2837 SoC devices used in all Raspberry Pi.

这个是一个写给BCM283X系列芯片的C语言库,适合那些具有C语言基础,在接触树莓派之前已经接触过单片机或者嵌入式开发的人群。wiringPi的API函数和arduino非常相似,这也使得它广受欢迎。作者给出了大量的说明和示例代码,这些示例代码也包括UART设备,I2C设备和SPI设备等。

 

 

BCM2835 C Library

 

This is a C library for Raspberry Pi (RPi). It provides access to GPIO and other IO functions on the Broadcom BCM 2835 chip, as used in the RaspberryPi, allowing access to the GPIO pins on the 26 pin IDE plug on the RPi board so you can control and interface with various external devices.

It provides functions for reading digital inputs and setting digital outputs, using SPI and I2C, and for accessing the system timers. Pin event detection is supported by polling (interrupts are not supported).

这个也是一个树莓派使用的C库,可以看做是驱动层。通过这个库可以学习 BCM2835 相关的寄存器操作。相对来说这个库是最底层的。

 

 

编程针脚

 

使用不同的库在编程时候对应的针脚的编号是不一样的,所以要根据库来设置针脚的编号。网上的图感觉标注的都不是很清楚,所以自己重新画了一个

 

 

Windows10 IoT 

 

上面这些库主要都是在树莓派的Linux类系统上使用。如果安装的是Windows10 IoT系统,可以使用C#开发UWP程序。所以Windows10 IoT也提供了完整SDK来和GPIO进行通信。

namespace BlinkyHeadlessCS
{
    public sealed class StartupTask : IBackgroundTask
    {
        BackgroundTaskDeferral deferral;
        private GpioPinValue value = GpioPinValue.High;
        private const int LED_PIN = 5;
        private GpioPin pin;
        private ThreadPoolTimer timer;

        public void Run(IBackgroundTaskInstance taskInstance)        {
            deferral = taskInstance.GetDeferral();
            InitGPIO();
            timer = ThreadPoolTimer.CreatePeriodicTimer(Timer_Tick, TimeSpan.FromMilliseconds(500));

        }
        private void InitGPIO()
        {
            pin = GpioController.GetDefault().OpenPin(LED_PIN);
            pin.Write(GpioPinValue.High);
            pin.SetDriveMode(GpioPinDriveMode.Output);
        }

        private void Timer_Tick(ThreadPoolTimer timer)
        {
            value = (value == GpioPinValue.High) ? GpioPinValue.Low : GpioPinValue.High;
            pin.Write(value);
        }
    }
}

除此之外还支持多种开发语言。

  • Languages
    • C#
    • C++
    • Javascript
    • Visual Basic

不过开发UWP程序需要安装Windows10, 使用VS进行开发。目前树莓派3B+还无法安装Windows10 IOT。

 

 

Android Things

 

同样google也发布了Android Things系统,树莓派可以通过Java代码来操作GPIO接口。和写Android一样,很熟悉的个感觉。 不过目前树莓派3B+同样也是不支持。

public class HomeActivity extends Activity {
    // GPIO Pin Name
    private static final String GPIO_NAME = ...;

    private Gpio mGpio;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Attempt to access the GPIO
        try {
            PeripheralManager manager = PeripheralManager.getInstance();
            mGpio = manager.openGpio(GPIO_NAME);
        } catch (IOException e) {
             Log.w(TAG, "Unable to access GPIO", e);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        if (mGpio != null) {
            try {
                mGpio.close();
                mGpio = null;
            } catch (IOException e) {
                Log.w(TAG, "Unable to close GPIO", e);
            }
        }
    }

    public void configureInput(Gpio gpio) throws IOException {
      // Initialize the pin as an input
      gpio.setDirection(Gpio.DIRECTION_IN);
      // High voltage is considered active
      gpio.setActiveType(Gpio.ACTIVE_HIGH);

      // Read the active high pin state
      if (gpio.getValue()) {
          // Pin is HIGH
      } else {
          // Pin is LOW
      }
  }
}

总体来说各个平台或库对GPIO的操作是完全一致的。只是使用了自己的语言进行了封装。

 


如果本文对您有帮助,可以扫描下方二维码打赏!您的支持是我的动力!
微信打赏 支付宝打赏

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注