第一个嵌入式程序 #

一、项目目标 #

本章我们将完成嵌入式开发的"Hello World"——让LED灯闪烁。通过这个简单的项目,你将学习:

  • GPIO基本概念
  • 硬件电路连接
  • Pi4J库的使用
  • 嵌入式程序的基本结构

二、GPIO基础 #

2.1 什么是GPIO #

GPIO(General Purpose Input/Output,通用输入输出)是嵌入式系统与外部世界交互的基本接口。

text
┌─────────────────────────────────────────┐
│              GPIO引脚功能               │
├─────────────────────────────────────────┤
│  输出模式:控制LED、继电器、蜂鸣器等    │
│  输入模式:读取按键、传感器信号等       │
│  特殊功能:PWM、I2C、SPI、UART等        │
└─────────────────────────────────────────┘

2.2 Raspberry Pi GPIO引脚图 #

text
    3.3V  ──[01] [02]──  5V
   GPIO2  ──[03] [04]──  5V
   GPIO3  ──[05] [06]──  GND
   GPIO4  ──[07] [08]──  GPIO14
     GND  ──[09] [10]──  GPIO15
  GPIO17  ──[11] [12]──  GPIO18
  GPIO27  ──[13] [14]──  GND
  GPIO22  ──[15] [16]──  GPIO23
    3.3V  ──[17] [18]──  GPIO24
   GPIO10  ──[19] [20]──  GND
    GPIO9  ──[21] [22]──  GPIO25
   GPIO11  ──[23] [24]──  GPIO8
     GND  ──[25] [26]──  GPIO7
    GPIO0  ──[27] [28]──  GPIO1
    GPIO5  ──[29] [30]──  GND
    GPIO6  ──[31] [32]──  GPIO12
   GPIO13  ──[33] [34]──  GND
   GPIO19  ──[35] [36]──  GPIO16
   GPIO26  ──[37] [38]──  GPIO20
     GND  ──[39] [40]──  GPIO21

2.3 GPIO编号方式 #

编号方式 说明 示例
BCM编号 Broadcom芯片引脚号 GPIO17
物理编号 板上引脚位置编号 Pin 11
WiringPi WiringPi库编号 Pin 0

本教程使用BCM编号方式。

三、硬件准备 #

3.1 所需材料 #

材料 数量 说明
Raspberry Pi 1 任意型号
LED 1 任意颜色
电阻 1 220Ω-330Ω
面包板 1 用于连接
杜邦线 2 公对母

3.2 电路连接 #

text
                    Raspberry Pi
                   ┌─────────────┐
                   │             │
    GPIO17 ────────┤ Pin 11      │
                   │             │
                   │         GND ├───────┐
                   │             │       │
                   └─────────────┘       │
                                         │
    ┌─────────────────────────────────────┘
    │
    │   ┌───────┐      ┌───────┐
    └───┤  LED  ├──────┤ 220Ω  ├───┐
        └───────┘      └───────┘   │
                                  │
    电路说明:                      │
    GPIO17 ── LED正极 ── LED负极 ── 电阻 ── GND

连接步骤:

  1. 将LED长脚(正极)连接到GPIO17(Pin 11)
  2. 将LED短脚(负极)连接到电阻一端
  3. 将电阻另一端连接到GND(Pin 6或Pin 9)

四、Pi4J库介绍 #

4.1 Pi4J概述 #

Pi4J是Raspberry Pi上最流行的Java硬件控制库,提供:

  • GPIO数字输入输出
  • PWM输出
  • I2C/SPI/UART通信
  • 事件监听机制

4.2 添加依赖 #

xml
<!-- pom.xml -->
<dependencies>
    <dependency>
        <groupId>com.pi4j</groupId>
        <artifactId>pi4j-core</artifactId>
        <version>2.3.0</version>
    </dependency>
    <dependency>
        <groupId>com.pi4j</groupId>
        <artifactId>pi4j-plugin-raspberrypi</artifactId>
        <version>2.3.0</version>
    </dependency>
    <dependency>
        <groupId>com.pi4j</groupId>
        <artifactId>pi4j-plugin-pigpio</artifactId>
        <version>2.3.0</version>
    </dependency>
</dependencies>

五、编写程序 #

5.1 基础版本:LED闪烁 #

java
package com.example.led;

import com.pi4j.Pi4J;
import com.pi4j.io.gpio.digital.DigitalOutput;
import com.pi4j.io.gpio.digital.DigitalState;

public class LedBlink {

    private static final int PIN_LED = 17;
    
    public static void main(String[] args) throws InterruptedException {
        System.out.println("LED闪烁程序启动...");
        
        var pi4j = Pi4J.newAutoContext();
        
        var led = pi4j.create(DigitalOutput.newConfigBuilder(pi4j)
                .id("led")
                .name("LED Flasher")
                .address(PIN_LED)
                .shutdown(DigitalState.LOW)
                .initial(DigitalState.LOW)
                .provider("pigpio-digital-output"));
        
        System.out.println("LED控制程序运行中,按Ctrl+C退出");
        
        while (true) {
            led.high();
            System.out.println("LED: ON");
            Thread.sleep(1000);
            
            led.low();
            System.out.println("LED: OFF");
            Thread.sleep(1000);
        }
    }
}

5.2 进阶版本:可控闪烁 #

java
package com.example.led;

import com.pi4j.Pi4J;
import com.pi4j.io.gpio.digital.DigitalOutput;
import com.pi4j.io.gpio.digital.DigitalState;

import java.util.Scanner;

public class ControlledBlink {

    private static final int PIN_LED = 17;
    private static volatile boolean running = true;
    
    public static void main(String[] args) {
        System.out.println("=== 可控LED闪烁程序 ===");
        System.out.println("命令: start | stop | speed <毫秒> | exit");
        
        var pi4j = Pi4J.newAutoContext();
        
        var led = pi4j.create(DigitalOutput.newConfigBuilder(pi4j)
                .id("led")
                .name("LED Controller")
                .address(PIN_LED)
                .shutdown(DigitalState.LOW)
                .initial(DigitalState.LOW)
                .provider("pigpio-digital-output"));
        
        BlinkController controller = new BlinkController(led);
        Thread blinkThread = new Thread(controller);
        blinkThread.start();
        
        Scanner scanner = new Scanner(System.in);
        
        while (running) {
            String input = scanner.nextLine().trim().toLowerCase();
            String[] parts = input.split("\\s+");
            
            switch (parts[0]) {
                case "start":
                    controller.start();
                    System.out.println("闪烁已启动");
                    break;
                case "stop":
                    controller.stop();
                    led.low();
                    System.out.println("闪烁已停止");
                    break;
                case "speed":
                    if (parts.length > 1) {
                        try {
                            int speed = Integer.parseInt(parts[1]);
                            controller.setSpeed(speed);
                            System.out.println("闪烁速度设置为 " + speed + "ms");
                        } catch (NumberFormatException e) {
                            System.out.println("无效的速度值");
                        }
                    }
                    break;
                case "exit":
                    running = false;
                    controller.stop();
                    led.low();
                    System.out.println("程序退出");
                    break;
                default:
                    System.out.println("未知命令");
            }
        }
        
        pi4j.shutdown();
        scanner.close();
    }
    
    static class BlinkController implements Runnable {
        private final DigitalOutput led;
        private volatile boolean blinking = false;
        private int speedMs = 500;
        
        public BlinkController(DigitalOutput led) {
            this.led = led;
        }
        
        @Override
        public void run() {
            while (running) {
                if (blinking) {
                    led.toggle();
                }
                try {
                    Thread.sleep(speedMs);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
        
        public void start() {
            blinking = true;
        }
        
        public void stop() {
            blinking = false;
        }
        
        public void setSpeed(int speedMs) {
            this.speedMs = Math.max(50, speedMs);
        }
    }
}

5.3 完整版本:多LED控制 #

java
package com.example.led;

import com.pi4j.Pi4J;
import com.pi4j.io.gpio.digital.DigitalOutput;
import com.pi4j.io.gpio.digital.DigitalState;
import com.pi4j.io.gpio.digital.DigitalOutputConfigBuilder;

import java.util.ArrayList;
import java.util.List;

public class MultiLedDemo {

    private static final int[] LED_PINS = {17, 18, 22, 23};
    
    public static void main(String[] args) throws InterruptedException {
        System.out.println("=== 多LED演示程序 ===\n");
        
        var pi4j = Pi4J.newAutoContext();
        List<DigitalOutput> leds = new ArrayList<>();
        
        for (int i = 0; i < LED_PINS.length; i++) {
            var led = pi4j.create(DigitalOutput.newConfigBuilder(pi4j)
                    .id("led-" + i)
                    .name("LED " + i)
                    .address(LED_PINS[i])
                    .shutdown(DigitalState.LOW)
                    .initial(DigitalState.LOW)
                    .provider("pigpio-digital-output"));
            leds.add(led);
        }
        
        System.out.println("1. 流水灯效果");
        runningLight(leds, 3);
        
        System.out.println("\n2. 来回闪烁效果");
        pingPong(leds, 3);
        
        System.out.println("\n3. 二进制计数效果");
        binaryCounter(leds, 16);
        
        System.out.println("\n4. 呼吸灯效果(模拟)");
        breathingEffect(leds.get(0), 5);
        
        for (DigitalOutput led : leds) {
            led.low();
        }
        
        System.out.println("\n演示完成!");
        pi4j.shutdown();
    }
    
    private static void runningLight(List<DigitalOutput> leds, int cycles) 
            throws InterruptedException {
        for (int cycle = 0; cycle < cycles; cycle++) {
            for (DigitalOutput led : leds) {
                led.high();
                Thread.sleep(150);
                led.low();
            }
        }
    }
    
    private static void pingPong(List<DigitalOutput> leds, int cycles) 
            throws InterruptedException {
        for (int cycle = 0; cycle < cycles; cycle++) {
            for (int i = 0; i < leds.size(); i++) {
                leds.get(i).high();
                Thread.sleep(100);
                leds.get(i).low();
            }
            for (int i = leds.size() - 2; i > 0; i--) {
                leds.get(i).high();
                Thread.sleep(100);
                leds.get(i).low();
            }
        }
    }
    
    private static void binaryCounter(List<DigitalOutput> leds, int maxCount) 
            throws InterruptedException {
        for (int count = 0; count < maxCount; count++) {
            System.out.print("计数: " + count + " -> ");
            for (int i = 0; i < leds.size(); i++) {
                boolean bit = ((count >> i) & 1) == 1;
                if (bit) {
                    leds.get(i).high();
                    System.out.print("1");
                } else {
                    leds.get(i).low();
                    System.out.print("0");
                }
            }
            System.out.println();
            Thread.sleep(500);
        }
    }
    
    private static void breathingEffect(DigitalOutput led, int cycles) 
            throws InterruptedException {
        for (int cycle = 0; cycle < cycles; cycle++) {
            for (int i = 0; i < 10; i++) {
                led.high();
                Thread.sleep(i * 2);
                led.low();
                Thread.sleep((10 - i) * 2);
            }
            for (int i = 10; i > 0; i--) {
                led.high();
                Thread.sleep(i * 2);
                led.low();
                Thread.sleep((10 - i) * 2);
            }
        }
        led.low();
    }
}

六、编译与运行 #

6.1 编译项目 #

bash
# 在开发主机上编译
mvn clean package

# 生成的JAR文件位于 target/ 目录

6.2 部署到设备 #

bash
# 使用SCP传输
scp target/led-blink-1.0-SNAPSHOT.jar pi@192.168.1.100:/home/pi/

# 或使用rsync
rsync -avz target/led-blink-1.0-SNAPSHOT.jar pi@192.168.1.100:/home/pi/

6.3 运行程序 #

bash
# SSH登录到设备
ssh pi@192.168.1.100

# 运行程序
java -jar led-blink-1.0-SNAPSHOT.jar

# 后台运行
nohup java -jar led-blink-1.0-SNAPSHOT.jar > led.log 2>&1 &

# 查看日志
tail -f led.log

6.4 开机自启动 #

创建systemd服务:

bash
# 创建服务文件
sudo nano /etc/systemd/system/led-blink.service
ini
[Unit]
Description=LED Blink Service
After=network.target

[Service]
Type=simple
User=pi
WorkingDirectory=/home/pi
ExecStart=/usr/bin/java -jar /home/pi/led-blink-1.0-SNAPSHOT.jar
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
bash
# 启用服务
sudo systemctl daemon-reload
sudo systemctl enable led-blink.service
sudo systemctl start led-blink.service

# 查看状态
sudo systemctl status led-blink.service

七、常见问题 #

7.1 LED不亮 #

检查清单:

  1. LED极性是否正确(长脚为正极)
  2. 电阻阻值是否合适
  3. GPIO引脚号是否正确
  4. 程序是否正确运行
bash
# 测试GPIO(命令行方式)
echo 17 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio17/direction
echo 1 > /sys/class/gpio/gpio17/value
echo 0 > /sys/class/gpio/gpio17/value

7.2 权限错误 #

bash
# 确保用户在正确的组
groups pi

# 添加到gpio组
sudo usermod -a -G gpio pi

# 重新登录生效
logout

7.3 pigpio服务问题 #

bash
# 检查pigpio服务状态
sudo systemctl status pigpiod

# 重启服务
sudo systemctl restart pigpiod

# 查看日志
journalctl -u pigpiod -f

八、代码结构说明 #

8.1 Pi4J核心对象 #

java
// Pi4J上下文 - 主入口点
var pi4j = Pi4J.newAutoContext();

// 数字输出配置
DigitalOutput.newConfigBuilder(pi4j)
    .id("unique-id")           // 唯一标识符
    .name("Friendly Name")     // 友好名称
    .address(17)               // GPIO引脚号
    .shutdown(DigitalState.LOW)// 关闭时状态
    .initial(DigitalState.LOW) // 初始状态
    .provider("pigpio-digital-output"); // 提供者

// 常用方法
led.high();      // 输出高电平
led.low();       // 输出低电平
led.toggle();    // 切换状态
led.state();     // 获取当前状态

8.2 程序生命周期 #

text
初始化 ──▶ 创建Pi4J上下文 ──▶ 配置GPIO ──▶ 主循环 ──▶ 清理资源

九、总结 #

通过本章的学习,你已经掌握了:

  • GPIO的基本概念和使用方法
  • 硬件电路的连接技巧
  • Pi4J库的基本使用
  • LED控制的多种实现方式

关键要点:

  1. 安全第一:连接电路前断电,使用合适的电阻保护GPIO
  2. 正确编号:使用BCM编号方式,避免引脚混淆
  3. 资源管理:程序退出时正确关闭GPIO和Pi4J上下文
  4. 异常处理:添加适当的异常处理和日志记录

下一章我们将学习JVM在嵌入式环境下的优化技巧,让Java程序在资源受限的设备上运行得更高效。

最后更新:2026-03-27