树莓派学习笔记(三):外设开发综述

树莓派外设开发

树莓派的接口

说到外设,前面51单片机课程部分最常见到的就是IO口:

对于主控芯片而言,外界环境的数据,往内传入就是Input;向各种开关、控制器,往外输出就是Output。

Input的外设,多为各种传感器:烟雾传感器、温湿度传感器、振动传感器、加速度传感器……

Output的外设,多为开关类:继电器、蜂鸣器等。

根据MCU与外设之间的关系,当IO口资源不足时,通常需要对IO口进行复用,即同一个IO口,根据所接外设不同,表现出不同的特性,可能作输出,也可能作输入

除了普通IO口(即通用的高低电平输入输出),树莓派的针脚还可用于PWM波输出、串口IICSPIIIS等协议的输出,这些口根据具体使用开发板的不同,一般都有特定硬件接口规定。

与C51、STM32、WemosD1等裸机直接操作IO口不同,树莓派、香橙派等开发板需要运行操作系统,对IO的操作一般与“驱动”挂钩。不同厂家生产的不同外设产品并非都是使用相同的协议,使用的时候就要根据实际情况,使用相应的协议进行编程。

WiringPi的安装与基本使用

安装

了解了树莓派有哪些接口后,这一小节来认识一个基于C语言开发,用于树莓派外设开发的函数库——wiringPi,其对GPIO控制,串口通信,中断等提供了丰富的接口,可以极大方便我们的开发。

官网地址为http://wiringpi.com,官方自身定位:GPIO Interface library for the Raspberry Pi

在树莓派前期的学习过程中,主要就是调用wiringPi库提供的的现成API,随着课程深入会逐渐学习自己去实现上层和底层驱动的代码

首先执行如下命令从官方仓库获取wiringPi(由于官方git仓库2019年已经停止更新,所以无法再从官网web端进行下载):

  • 32位系统,执行以下命令获取编译好的版本:

    1
    2
    3
    4
    cd /tmp
    wget https://project-downloads.drogon.net/wiringpi-latest.deb
    sudo apt --fix-broken install #可能有一些底层依赖没有装好,所以使用apt提前修复好
    sudo dpkg -i wiringpi-latest.deb
  • 64位系统,由于2019年时树莓派还没有64位系统,所以需要自己下载源码编译(下面的是非官方仓库):

    1
    2
    3
    4
    5
    sudo apt update
    sudo apt install build-essential
    git clone https://github.com/WiringPi/WiringPi.git
    cd WiringPi
    ./build

安装好后,使用gpio -v检查是否安装好:

引用wiringPi提供函数的方法
  1. 所有WiringPi提供的函数,都可通过#include <wiringPi.h>的头文件获得;

  2. 使用wiringPi提供的函数,必须在执行任何操作前初始化树莓派,否则程序不能正常工作。

    1
    2
    3
    int wiringPiSetup (void)
    //返回执行状态,-1表示失败
    //成功则初始化编号0-16的wiringPi引脚,具体对照表见下方

通过gpio readall,可以得到树莓派40pin与wiringPi各种口的对照表为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
+-----+-----+---------+------+---+---Pi 3B--+---+------+---------+-----+-----+
| BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
| | | 3.3v | | | 1 || 2 | | | 5v | | |
| 2 | 8 | SDA.1 | IN | 1 | 3 || 4 | | | 5v | | |
| 3 | 9 | SCL.1 | IN | 1 | 5 || 6 | | | 0v | | |
| 4 | 7 | GPIO. 7 | IN | 1 | 7 || 8 | 0 | ALT0 | TxD | 15 | 14 |
| | | 0v | | | 9 || 10 | 1 | ALT0 | RxD | 16 | 15 |
| 17 | 0 | GPIO. 0 | IN | 0 | 11 || 12 | 0 | IN | GPIO. 1 | 1 | 18 |
| 27 | 2 | GPIO. 2 | IN | 0 | 13 || 14 | | | 0v | | |
| 22 | 3 | GPIO. 3 | IN | 0 | 15 || 16 | 0 | IN | GPIO. 4 | 4 | 23 |
| | | 3.3v | | | 17 || 18 | 0 | IN | GPIO. 5 | 5 | 24 |
| 10 | 12 | MOSI | IN | 0 | 19 || 20 | | | 0v | | |
| 9 | 13 | MISO | IN | 0 | 21 || 22 | 0 | IN | GPIO. 6 | 6 | 25 |
| 11 | 14 | SCLK | IN | 0 | 23 || 24 | 1 | IN | CE0 | 10 | 8 |
| | | 0v | | | 25 || 26 | 1 | IN | CE1 | 11 | 7 |
| 0 | 30 | SDA.0 | IN | 1 | 27 || 28 | 1 | IN | SCL.0 | 31 | 1 |
| 5 | 21 | GPIO.21 | IN | 1 | 29 || 30 | | | 0v | | |
| 6 | 22 | GPIO.22 | IN | 1 | 31 || 32 | 0 | IN | GPIO.26 | 26 | 12 |
| 13 | 23 | GPIO.23 | IN | 0 | 33 || 34 | | | 0v | | |
| 19 | 24 | GPIO.24 | IN | 0 | 35 || 36 | 0 | IN | GPIO.27 | 27 | 16 |
| 26 | 25 | GPIO.25 | IN | 0 | 37 || 38 | 0 | IN | GPIO.28 | 28 | 20 |
| | | 0v | | | 39 || 40 | 0 | IN | GPIO.29 | 29 | 21 |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
| BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM |
+-----+-----+---------+------+---+---Pi 3B--+---+------+---------+-----+-----+

关于wiringPi具体提供哪些函数及其使用方法,参考树莓派wiringPi详解

树莓派控制继电器

首先回顾一下继电器的外观和原理:

继电器一端有三个输入口:VCC、GND用于供电,一个IN口作为信号控制输入;
另一端三个输出口:一个COM、一个NO和一个NC,其中NO为常开端,NC为常闭端,COM为公共端。

实际应用中,一般控制的元件直接电源负极相连,正极连接COM口;继电器NO口直接连接电源正极;

当输入IN口为低电平时,触发继电器COM口与常开的NO相接,从而连通电路,激活控制的元件

首先根据上面的针脚对应关系,我们将继电器的VCCGNDIN口分别用杜邦线接到开发板的物理针脚1号、9号、7号(对应GPIO编号也是七号,默认高电平),然后编写一个简单的小程序从键盘获取用户输入,进而控制输出电平的高低:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//replay.c
#include<wiringPi.h>
#include<stdio.h>
#define SWITCH 7
//使用宏方便后续针脚改动时代码更新

int main(){
if((wiringPiSetup())==-1){
printf("硬件连接初始化异常!");
return -1;
}
int choice;
pinMode(SWITCH,OUTPUT);
digitalWrite(SWITCH,HIGH);//默认断开
while(1){
printf("请选择,1:断开开关,0:闭合开关\n");
scanf("%d",&choice);
if(choice==0){
digitalWrite(SWITCH,LOW);
} else if(choice==1){
digitalWrite(SWITCH,HIGH);
} else {
printf("输入错误!\n");
}
}
}

运行过程中,随着选项的切换,能听到继电器发出的“咔哒”声,在电路导通时,能够听到接在电路中的报警器

从这个例子中,学到的知识点有:

  • 两个wiringPi提供的API:pinModedigitalWrite,用于引脚配置与电平输出
  • 继电器的控制方法,以及其具体的应用方式
  • 宏定义编程习惯
树莓派控制继电器组

单个继电器模块,输入端需要三个针脚,那实际应用中,有四个继电器就要插12根杜邦线吗?不必。用一个继电器组就能解决。以规格为4的继电器组为例,组合共有6个输入针脚:VCC、GND、IN1-IN4,其中VCC需5v电源,IN1-IN4分别为1到4号继电器的控制口。

首先还是参照gpio针脚对照图,将继电器组的4个针脚分别接入26、27、28、29四个口,然后主体流程还是参照上面的写法,不过由于要控制4个继电器,所以这里指令的输入和识别是个难题。我们固定指令的格式为num choice,num为继电器编号,choice为on/off代表闭合/断开。这里使用了strtok函数将两者分隔开。

点击展开
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
//replay.c
#include<wiringPi.h>
#include<stdio.h>
#include<string.h>
#define IN1 26
#define IN2 27
#define IN3 28
#define IN4 29

int main(){
if((wiringPiSetup())==-1){
printf("硬件连接初始化异常!");
return -1;
}

char num;
char cmd[6]={'\0'};
char *choice;
pinMode(IN1,OUTPUT);
pinMode(IN2,OUTPUT);
pinMode(IN3,OUTPUT);
pinMode(IN4,OUTPUT);

digitalWrite(IN1,HIGH);
digitalWrite(IN2,HIGH);
digitalWrite(IN3,HIGH);
digitalWrite(IN4,HIGH);
while(1){
printf("请输入指令:0/1/2/3/4 on/off\n");
gets(cmd);
char *switcher = strtok(cmd, " ");
num=switcher[0];
choice = strtok(NULL, " ");
printf("num:%c;choice:%s\n",num,choice);
switch(num){
case '1':
if(strcmp(choice,"on")==0){
digitalWrite(IN1,LOW);
} else if(strcmp(choice,"off")==0){
digitalWrite(IN1,HIGH);
} else {
printf("指令有误!\n");
}
break;
case '2':
if(strcmp(choice,"on")==0){
digitalWrite(IN2,LOW);
} else if(strcmp(choice,"off")==0){
digitalWrite(IN2,HIGH);
} else {
printf("指令有误!\n");
}
break;
case '3':
if(strcmp(choice,"on")==0){
digitalWrite(IN3,LOW);
} else if(strcmp(choice,"off")==0){
digitalWrite(IN3,HIGH);
} else {
printf("指令有误!\n");
}
break;
case '4':
if(strcmp(choice,"on")==0){
digitalWrite(IN4,LOW);
} else if(strcmp(choice,"off")==0){
digitalWrite(IN4,HIGH);
} else {
printf("指令有误!\n");
}
break;
case '0'://全体控制
if(strcmp(choice,"on")==0){
digitalWrite(IN1,LOW);
digitalWrite(IN2,LOW);
digitalWrite(IN3,LOW);
digitalWrite(IN4,LOW);
} else if(strcmp(choice,"off")==0){
digitalWrite(IN1,HIGH);
digitalWrite(IN2,HIGH);
digitalWrite(IN3,HIGH);
digitalWrite(IN4,HIGH);
} else {
printf("指令有误!\n");
}
break;
deafult:
printf("继电器选择错误!\n");
break;
}
memset(cmd,'\0',sizeof(cmd));
}
}

超声波模块

超声波测距原理

首先看一下超声波模块的实物图:

超声波模块

该模块共有VCC、GND、Echo、Trig四个接口,其中Trig为触发测距信号,对单片机来说是输出口,Echo为测距结果返回信号,对单片机来说是输入口。

模块的主要参数:

  • 工作电压与电流:5V,15mA。

  • 感测距离:2~400cm

  • 感测角度:不大于15°。

  • 被测物的面积不要小于50cm2并且尽量平整。

  • 具备温度补偿电路。

    在超声波模块的触发脚位输入10微秒以上的高电位,即可发射超声波,发射超声波之后,与接收到传回的超声波之前,“响应”脚位呈现高电位。因此,程序可从“响应”脚位的高电位脉冲持续时间,换算出被测物的距离。

下图是超声波模块的时序逻辑:

超声波测距时序逻辑

也即:

  • 测距信号触发方法:
    Trig,给Trig端口至少10us的高电平
  • 怎么知道开始发超声波了:
    Echo信号,由低电平跳转到高电平,表示开始发送波
  • 怎么知道接收了返回波:
    Echo,由高电平跳转回低电平,表示波回来了
  • 怎么算时间:
    Echo引脚维持高电平的时间,即:
    波发出去的那一下,开始启动定时器;
    波回来的拿一下,我们开始停止定时器,计算出中间经过多少时间
  • 怎么算距离:
    距离=速度(340m/s)*时间/2
测距编程实现

具体代码参照orangepiZero2超声波模块,主要注意几个关键点:

  1. wiringPiSetup()初始化
  2. pinMode()设置IO属性
  3. 时序逻辑,超声波发射与反射的标志以及如何体现
  4. 时间结构体timeval的内容

其他外设的开发,后续课程内容(智能家居部分)会进一步接触。


树莓派学习笔记(三):外设开发综述
https://dockingyuan.top/2023/01/05/raspberry/3-树莓派外设开发综述/
作者
Yuan Yuan
发布于
2023年1月5日
许可协议