树莓派外设开发
树莓派的接口
说到外设,前面51单片机课程部分最常见到的就是IO口:
对于主控芯片而言,外界环境的数据,往内传入就是Input;向各种开关、控制器,往外输出就是Output。
Input的外设,多为各种传感器:烟雾传感器、温湿度传感器、振动传感器、加速度传感器……
Output的外设,多为开关类:继电器、蜂鸣器等。
根据MCU与外设之间的关系,当IO口资源不足时,通常需要对IO口进行复用,即同一个IO口,根据所接外设不同,表现出不同的特性,可能作输出,也可能作输入
除了普通IO口(即通用的高低电平输入输出),树莓派的针脚还可用于PWM波输出、串口、IIC、SPI、IIS等协议的输出,这些口根据具体使用开发板的不同,一般都有特定硬件接口规定。
与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端进行下载):
安装好后,使用gpio -v
检查是否安装好:
引用wiringPi
提供函数的方法
所有WiringPi
提供的函数,都可通过#include <wiringPi.h>
的头文件获得;
使用wiringPi
提供的函数,必须在执行任何操作前初始化树莓派,否则程序不能正常工作。
1 2 3
| int wiringPiSetup (void)
|
通过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相接,从而连通电路,激活控制的元件
首先根据上面的针脚对应关系,我们将继电器的VCC
、GND
、IN
口分别用杜邦线接到开发板的物理针脚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
| #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:pinMode
和digitalWrite
,用于引脚配置与电平输出
- 继电器的控制方法,以及其具体的应用方式
- 宏定义编程习惯
树莓派控制继电器组
单个继电器模块,输入端需要三个针脚,那实际应用中,有四个继电器就要插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
| #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为测距结果返回信号,对单片机来说是输入口。
模块的主要参数:
下图是超声波模块的时序逻辑:

也即:
- 测距信号触发方法:
Trig,给Trig端口至少10us的高电平
- 怎么知道开始发超声波了:
Echo信号,由低电平跳转到高电平,表示开始发送波
- 怎么知道接收了返回波:
Echo,由高电平跳转回低电平,表示波回来了
- 怎么算时间:
Echo引脚维持高电平的时间,即:
波发出去的那一下,开始启动定时器;
波回来的拿一下,我们开始停止定时器,计算出中间经过多少时间
- 怎么算距离:
距离=速度(340m/s)*时间/2
测距编程实现
具体代码参照orangepiZero2超声波模块,主要注意几个关键点:
wiringPiSetup()
初始化
pinMode()
设置IO属性
- 时序逻辑,超声波发射与反射的标志以及如何体现
- 时间结构体
timeval
的内容
其他外设的开发,后续课程内容(智能家居部分)会进一步接触。