1. 初识wiringPi:树莓派GPIO编程利器
wiringPi是一个专为树莓派设计的GPIO控制库,它提供了类似Arduino的编程接口,让开发者能够轻松控制树莓派的GPIO引脚。这个库最初由Gordon Henderson开发,现在已经支持多种派系开发板,包括Orange Pi等兼容板卡。
我第一次接触wiringPi是在一个智能家居项目中,需要控制多个传感器和执行器。相比直接操作/sys/class/gpio的原始方式,wiringPi提供了更高层次的抽象,让代码更简洁易读。比如控制一个LED闪烁,只需要几行直观的代码:
c复制#include <wiringPi.h>
int main() {
wiringPiSetup();
pinMode(0, OUTPUT);
while(1) {
digitalWrite(0, HIGH);
delay(500);
digitalWrite(0, LOW);
delay(500);
}
}
2. wiringPi环境搭建与验证
2.1 安装wiringPi库
对于Orange Pi开发板,推荐使用wiringOP这个兼容库。以下是详细的安装步骤:
- 通过GitHub获取最新源码:
bash复制git clone https://github.com/orangepi-xunlong/wiringOP
- 编译安装:
bash复制cd wiringOP
sudo ./build
注意:编译前确保已安装必要的开发工具链(如gcc、make等)。如果遇到依赖问题,可以先执行
sudo apt update && sudo apt install build-essential
- 验证安装:
bash复制gpio readall
这个命令会显示一个引脚映射表,如果看到类似下面的输出,说明安装成功:
code复制 +-----+-----+---------+------+---+---Pi 3---+---+------+---------+-----+-----+
| BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
| | | 3.3v | | | 1 || 2 | | | 5v | | |
| 2 | 8 | SDA.1 | ALT0 | 1 | 3 || 4 | | | 5v | | |
| 3 | 9 | SCL.1 | ALT0 | 1 | 5 || 6 | | | 0v | | |
| 4 | 7 | GPIO. 7 | IN | 1 | 7 || 8 | 1 | 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 | ALT0 | 0 | 19 || 20 | | | 0v | | |
| 9 | 13 | MISO | ALT0 | 0 | 21 || 22 | 0 | IN | GPIO. 6 | 6 | 25 |
| 11 | 14 | SCLK | ALT0 | 0 | 23 || 24 | 1 | OUT | CE0 | 10 | 8 |
| | | 0v | | | 25 || 26 | 1 | OUT | CE1 | 11 | 7 |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
2.2 开发环境配置建议
在实际项目中,我推荐使用VS Code作为开发环境,配合Remote-SSH插件进行远程开发。这样可以在PC上编写代码,直接在开发板上编译运行。主要配置步骤:
- 安装VS Code和Remote-SSH插件
- 配置SSH连接到开发板
- 安装C/C++扩展用于代码提示
- 配置tasks.json用于编译:
json复制{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "shell",
"command": "gcc",
"args": [
"-o",
"${fileBasenameNoExtension}",
"${file}",
"-lwiringPi"
],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
3. wiringPi基础应用实例
3.1 GPIO控制与定时操作
下面是一个结合时间函数的蜂鸣器控制示例,展示了如何精确控制GPIO状态:
c复制#include <stdio.h>
#include <wiringPi.h>
#include <unistd.h>
#define BEEP_PIN 0 // 使用wiringPi引脚编号
int main(void) {
wiringPiSetup();
pinMode(BEEP_PIN, OUTPUT);
while(1) {
// 5秒高电平
digitalWrite(BEEP_PIN, HIGH);
usleep(5000000); // 微秒级延时
// 5秒低电平
digitalWrite(BEEP_PIN, LOW);
usleep(5000000);
}
return 0;
}
经验分享:usleep()提供微秒级延时,比sleep()的秒级更精确。但在实际使用中要注意:
- usleep参数不要超过1000000(1秒)
- 长时间延时建议用sleep和usleep组合
- 对于更精确的定时需求,建议使用硬件定时器
3.2 时间测量与性能分析
嵌入式开发中经常需要测量代码执行时间。下面是一个计算函数执行时间的实用示例:
c复制#include <sys/time.h>
#include <stdio.h>
void testFunction() {
// 模拟耗时操作
volatile int i, j;
for(i = 0; i < 100; i++) {
for(j = 0; j < 1000; j++);
}
}
int main() {
struct timeval start, end;
gettimeofday(&start, NULL);
testFunction();
gettimeofday(&end, NULL);
long duration = (end.tv_sec - start.tv_sec) * 1000000 +
(end.tv_usec - start.tv_usec);
printf("testFunction执行耗时: %ld微秒\n", duration);
return 0;
}
这个技术在优化算法性能时非常有用。我在一个图像处理项目中就用它找出了瓶颈函数,优化后性能提升了30%。
4. 传感器应用实战
4.1 HC-SR04超声波测距
HC-SR04是常用的超声波测距模块,通过wiringPi可以方便地驱动:
c复制#include <stdio.h>
#include <sys/time.h>
#include <wiringPi.h>
#define TRIG_PIN 0
#define ECHO_PIN 1
double getDistance() {
struct timeval start, stop;
// 设置引脚模式
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
// 发送10us触发脉冲
digitalWrite(TRIG_PIN, LOW);
usleep(2);
digitalWrite(TRIG_PIN, HIGH);
usleep(10);
digitalWrite(TRIG_PIN, LOW);
// 等待回波信号
while(!digitalRead(ECHO_PIN));
gettimeofday(&start, NULL);
while(digitalRead(ECHO_PIN));
gettimeofday(&stop, NULL);
// 计算距离(声速340m/s)
long duration = (stop.tv_sec - start.tv_sec) * 1000000 +
(stop.tv_usec - start.tv_usec);
return (double)duration * 34000 / 2000000; // 单位cm
}
int main() {
wiringPiSetup();
while(1) {
double distance = getDistance();
printf("距离: %.2f cm\n", distance);
usleep(500000); // 500ms间隔
}
return 0;
}
避坑指南:
- 测量时避免障碍物太近(<2cm)或太远(>400cm)
- 多次测量取平均值可提高精度
- 注意温度对声速的影响,必要时进行温度补偿
4.2 SG90舵机控制
SG90是一款小型舵机,通过PWM信号控制角度:
c复制#include <wiringPi.h>
#include <signal.h>
#include <sys/time.h>
#define SERVO_PIN 5
static int count = 0;
int angle = 0;
void timerHandler(int signum) {
count++;
if(count <= angle) {
digitalWrite(SERVO_PIN, HIGH);
} else {
digitalWrite(SERVO_PIN, LOW);
}
if(count == 40) count = 0; // 20ms周期
}
int main() {
wiringPiSetup();
pinMode(SERVO_PIN, OUTPUT);
// 配置定时器(500us中断)
struct itimerval itv;
itv.it_value.tv_sec = 1;
itv.it_value.tv_usec = 0;
itv.it_interval.tv_sec = 0;
itv.it_interval.tv_usec = 500;
setitimer(ITIMER_REAL, &itv, NULL);
signal(SIGALRM, timerHandler);
while(1) {
printf("输入角度(0-180): ");
scanf("%d", &angle);
angle = angle / 180.0 * 40; // 转换为0-40范围
}
return 0;
}
舵机控制的关键是理解PWM信号:
- 周期通常为20ms(50Hz)
- 高电平持续时间0.5ms-2.5ms对应0-180度
- 占空比变化控制角度
5. 通信接口应用
5.1 I2C驱动OLED显示
I2C是一种常用的双线制串行总线,下面是通过I2C控制OLED显示的示例:
c复制#include "oled.h"
#include "font.h"
void showMessage(struct display_info *disp, const char *msg) {
disp->font = font1;
oled_putstrto(disp, 0, 10, msg);
disp->font = font2;
oled_putstrto(disp, 0, 20, msg);
disp->font = font3;
oled_putstrto(disp, 0, 30, msg);
oled_send_buffer(disp);
}
int main(int argc, char **argv) {
if(argc < 2) {
printf("Usage: %s <i2c-device>\n", argv[0]);
return -1;
}
struct display_info disp;
memset(&disp, 0, sizeof(disp));
disp.address = OLED_I2C_ADDR;
disp.font = font2;
oled_open(&disp, argv[1]);
oled_init(&disp);
showMessage(&disp, "Hello Orange Pi!");
return 0;
}
关键点说明:
- 首先确认I2C设备节点(如/dev/i2c-3)
- 使用i2c-tools检测设备地址:
bash复制
i2cdetect -y 3 - OLED通常需要初始化命令序列
- 显示内容先写入缓冲区,再统一刷新
5.2 串口通信实现
串口是嵌入式系统中最常用的通信接口之一。以下是两种实现方式:
5.2.1 使用wiringPi库
c复制#include <wiringSerial.h>
#include <pthread.h>
int fd;
void* sendThread(void *arg) {
char buf[32];
while(1) {
scanf("%s", buf);
serialPuts(fd, buf);
}
}
void* receiveThread(void *arg) {
while(1) {
while(serialDataAvail(fd)) {
putchar(serialGetchar(fd));
}
}
}
int main() {
if((fd = serialOpen("/dev/ttyS5", 115200)) < 0) {
perror("serialOpen");
return -1;
}
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, sendThread, NULL);
pthread_create(&tid2, NULL, receiveThread, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
serialClose(fd);
return 0;
}
5.2.2 直接使用系统调用
c复制#include <termios.h>
#include <fcntl.h>
int serialOpen(const char *device, int baud) {
struct termios options;
int fd = open(device, O_RDWR | O_NOCTTY);
tcgetattr(fd, &options);
cfsetispeed(&options, baud);
cfsetospeed(&options, baud);
options.c_cflag |= (CLOCAL | CREAD);
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
tcsetattr(fd, TCSANOW, &options);
return fd;
}
串口开发注意事项:
- 正确设置波特率、数据位、停止位和校验位
- 注意缓冲区大小和读取策略
- 多线程环境下注意同步问题
- 长时间通信建议加入心跳机制
6. 综合项目实例:手机远程控制
下面是一个结合串口和ADB控制手机的实用示例:
c复制#include "uartTool.h"
#include <pthread.h>
int fd;
void* readSerial() {
char cmd;
while(1) {
cmd = serialGetchar(fd);
switch(cmd) {
case 'N': // 下一页
system("adb shell input swipe 540 1300 540 500 100");
break;
case 'P': // 上一页
system("adb shell input swipe 540 500 540 1300 100");
break;
case 'Z': // 点赞
system("adb shell \"seq 3 | while read i;do input tap 350 1050 & sleep 0.01;done;\"");
break;
case 'Q': // 锁屏
system("adb shell input keyevent 26");
break;
}
}
}
int main(int argc, char **argv) {
if(argc < 2) {
printf("Usage: %s /dev/ttyS?\n", argv[0]);
return -1;
}
if((fd = serialOpen(argv[1], 115200)) == -1) {
perror("serialOpen");
return -1;
}
pthread_t tid;
pthread_create(&tid, NULL, readSerial, NULL);
while(1) sleep(10);
return 0;
}
项目搭建步骤:
- 连接手机到开发板
- 安装ADB工具:
bash复制sudo apt install adb - 配置USB权限:
bash复制echo 'SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", MODE="0666"' | sudo tee /etc/udev/rules.d/51-android.rules sudo udevadm control --reload - 在手机上启用USB调试模式
- 编译并运行控制程序
7. 开发经验与优化建议
7.1 常见问题排查
-
GPIO无响应:
- 检查引脚编号是否正确(wiringPi编号与物理引脚区别)
- 确认用户是否有操作GPIO的权限(需root或gpio组)
- 用万用表测量引脚电压
-
I2C设备检测不到:
bash复制
i2cdetect -y 3- 确认设备地址正确
- 检查上拉电阻
- 确认SCL/SDA线连接可靠
-
串口数据乱码:
- 确认双方波特率一致
- 检查地线连接
- 尝试降低波特率测试
7.2 性能优化技巧
-
减少不必要的延时,改用中断或事件驱动
-
对时间敏感的操作使用硬件PWM/定时器
-
多线程应用中:
- 避免共享资源竞争
- 使用互斥锁保护关键区域
- 考虑使用消息队列通信
-
电源管理:
- 不用的外设及时断电
- 适当降低工作频率
- 使用睡眠模式
7.3 扩展学习建议
- 深入学习Linux设备驱动开发
- 了解设备树(Device Tree)配置
- 掌握示波器等调试工具的使用
- 研究RTOS与Linux的差异与选择
通过实际项目验证,wiringPi库虽然简单易用,但在复杂项目中可能会遇到性能瓶颈。这时可以考虑直接操作寄存器或使用Linux内核提供的GPIO、PWM等子系统接口,获得更高的灵活性和性能。