PWM与ADC #

一、PWM基础概念 #

1.1 PWM概述 #

PWM(Pulse Width Modulation,脉冲宽度调制)是一种通过改变脉冲宽度来控制平均输出电压的技术。

text
┌─────────────────────────────────────────────────────────┐
│                    PWM波形示意                           │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  占空比 25%                                             │
│  ┌──┐                                                  │
│  │  │                                                  │
│ ─┘  └──────────────────────────────────────────────    │
│                                                         │
│  占空比 50%                                             │
│  ┌────┐                                                │
│  │    │                                                │
│ ─┘    └───────────────────────────────────────────     │
│                                                         │
│  占空比 75%                                             │
│  ┌──────┐                                              │
│  │      │                                              │
│ ─┘      └─────────────────────────────────────────     │
│                                                         │
│  占空比 = 高电平时间 / 周期 × 100%                       │
│  平均电压 = 供电电压 × 占空比                            │
│                                                         │
└─────────────────────────────────────────────────────────┘

1.2 PWM参数 #

参数 说明 单位
频率 每秒脉冲次数 Hz
周期 一个完整脉冲的时间
占空比 高电平时间占比 %
分辨率 占空比调节精度

1.3 PWM应用场景 #

应用 频率范围 说明
LED调光 100Hz-1kHz 避免可见闪烁
舵机控制 50Hz 标准舵机频率
直流电机 1kHz-20kHz 平滑调速
音频播放 20kHz以上 需要滤波

二、Pi4J PWM编程 #

2.1 软件PWM #

java
package com.example.pwm;

import com.pi4j.Pi4J;
import com.pi4j.io.pwm.Pwm;
import com.pi4j.io.pwm.PwmConfig;
import com.pi4j.io.pwm.PwmType;

public class SoftwarePWMDemo {

    private static final int PIN_PWM = 18;
    
    public static void main(String[] args) throws InterruptedException {
        var pi4j = Pi4J.newAutoContext();
        
        Pwm pwm = pi4j.create(Pwm.newConfigBuilder(pi4j)
                .id("pwm-led")
                .name("PWM LED")
                .address(PIN_PWM)
                .pwmType(PwmType.SOFTWARE)
                .frequency(1000)
                .dutyCycle(0)
                .shutdown(0)
                .initial(0)
                .build());
        
        System.out.println("PWM软件模拟演示");
        
        pwm.on(1000, 25);
        System.out.println("占空比: 25%");
        Thread.sleep(2000);
        
        pwm.on(1000, 50);
        System.out.println("占空比: 50%");
        Thread.sleep(2000);
        
        pwm.on(1000, 75);
        System.out.println("占空比: 75%");
        Thread.sleep(2000);
        
        pwm.on(1000, 100);
        System.out.println("占空比: 100%");
        Thread.sleep(2000);
        
        pwm.off();
        
        pi4j.shutdown();
    }
}

2.2 硬件PWM #

java
package com.example.pwm;

import com.pi4j.Pi4J;
import com.pi4j.io.pwm.Pwm;
import com.pi4j.io.pwm.PwmConfig;
import com.pi4j.io.pwm.PwmType;

public class HardwarePWMDemo {

    private static final int PWM_PIN = 18;
    
    public static void main(String[] args) throws InterruptedException {
        var pi4j = Pi4J.newAutoContext();
        
        Pwm pwm = pi4j.create(Pwm.newConfigBuilder(pi4j)
                .id("hw-pwm")
                .name("Hardware PWM")
                .address(PWM_PIN)
                .pwmType(PwmType.HARDWARE)
                .frequency(1000)
                .dutyCycle(0)
                .shutdown(0)
                .initial(0)
                .build());
        
        System.out.println("硬件PWM演示");
        
        for (int duty = 0; duty <= 100; duty += 10) {
            pwm.on(1000, duty);
            System.out.printf("频率: 1000Hz, 占空比: %d%%%n", duty);
            Thread.sleep(500);
        }
        
        pwm.off();
        pi4j.shutdown();
    }
}

2.3 LED呼吸灯效果 #

java
package com.example.pwm;

import com.pi4j.Pi4J;
import com.pi4j.io.pwm.Pwm;
import com.pi4j.io.pwm.PwmType;

public class BreathingLED {

    private static final int PWM_PIN = 18;
    private static final int PWM_FREQ = 1000;
    
    public static void main(String[] args) throws InterruptedException {
        var pi4j = Pi4J.newAutoContext();
        
        Pwm pwm = pi4j.create(Pwm.newConfigBuilder(pi4j)
                .id("breathing-led")
                .name("Breathing LED")
                .address(PWM_PIN)
                .pwmType(PwmType.HARDWARE)
                .frequency(PWM_FREQ)
                .dutyCycle(0)
                .build());
        
        System.out.println("LED呼吸灯效果(按Ctrl+C退出)");
        
        while (true) {
            for (int duty = 0; duty <= 100; duty++) {
                pwm.on(PWM_FREQ, duty);
                Thread.sleep(10);
            }
            
            for (int duty = 100; duty >= 0; duty--) {
                pwm.on(PWM_FREQ, duty);
                Thread.sleep(10);
            }
        }
    }
}

三、电机控制 #

3.1 直流电机控制 #

java
package com.example.pwm.motor;

import com.pi4j.Pi4J;
import com.pi4j.io.gpio.digital.DigitalOutput;
import com.pi4j.io.gpio.digital.DigitalState;
import com.pi4j.io.pwm.Pwm;
import com.pi4j.io.pwm.PwmType;

public class DCMotorController {

    private final Pwm pwm;
    private final DigitalOutput in1;
    private final DigitalOutput in2;
    
    public DCMotorController(var pi4j, int pwmPin, int in1Pin, int in2Pin) {
        this.pwm = pi4j.create(Pwm.newConfigBuilder(pi4j)
                .id("motor-pwm")
                .name("Motor PWM")
                .address(pwmPin)
                .pwmType(PwmType.HARDWARE)
                .frequency(10000)
                .dutyCycle(0)
                .build());
        
        this.in1 = pi4j.create(DigitalOutput.newConfigBuilder(pi4j)
                .id("motor-in1")
                .name("Motor IN1")
                .address(in1Pin)
                .shutdown(DigitalState.LOW)
                .initial(DigitalState.LOW)
                .provider("pigpio-digital-output"));
        
        this.in2 = pi4j.create(DigitalOutput.newConfigBuilder(pi4j)
                .id("motor-in2")
                .name("Motor IN2")
                .address(in2Pin)
                .shutdown(DigitalState.LOW)
                .initial(DigitalState.LOW)
                .provider("pigpio-digital-output"));
    }
    
    public void setSpeed(int speed) {
        int dutyCycle = Math.min(100, Math.max(0, Math.abs(speed)));
        pwm.on(10000, dutyCycle);
    }
    
    public void forward(int speed) {
        in1.high();
        in2.low();
        setSpeed(speed);
    }
    
    public void backward(int speed) {
        in1.low();
        in2.high();
        setSpeed(speed);
    }
    
    public void stop() {
        in1.low();
        in2.low();
        pwm.on(10000, 0);
    }
    
    public void brake() {
        in1.high();
        in2.high();
        pwm.on(10000, 100);
    }
    
    public static void main(String[] args) throws InterruptedException {
        var pi4j = Pi4J.newAutoContext();
        
        DCMotorController motor = new DCMotorController(pi4j, 18, 17, 22);
        
        System.out.println("直流电机控制演示");
        
        System.out.println("正转加速...");
        for (int speed = 0; speed <= 100; speed += 10) {
            motor.forward(speed);
            Thread.sleep(200);
        }
        
        Thread.sleep(2000);
        
        System.out.println("减速停止...");
        for (int speed = 100; speed >= 0; speed -= 10) {
            motor.forward(speed);
            Thread.sleep(200);
        }
        
        motor.stop();
        
        pi4j.shutdown();
    }
}

3.2 舵机控制 #

java
package com.example.pwm.motor;

import com.pi4j.Pi4J;
import com.pi4j.io.pwm.Pwm;
import com.pi4j.io.pwm.PwmType;

public class ServoMotor {

    private static final int SERVO_FREQ = 50;
    private static final double MIN_DUTY = 2.5;
    private static final double MAX_DUTY = 12.5;
    
    private final Pwm pwm;
    private final int minAngle;
    private final int maxAngle;
    
    public ServoMotor(var pi4j, int pin, int minAngle, int maxAngle) {
        this.minAngle = minAngle;
        this.maxAngle = maxAngle;
        
        this.pwm = pi4j.create(Pwm.newConfigBuilder(pi4j)
                .id("servo")
                .name("Servo Motor")
                .address(pin)
                .pwmType(PwmType.HARDWARE)
                .frequency(SERVO_FREQ)
                .dutyCycle(0)
                .build());
    }
    
    public ServoMotor(var pi4j, int pin) {
        this(pi4j, pin, 0, 180);
    }
    
    public void setAngle(int angle) {
        angle = Math.max(minAngle, Math.min(maxAngle, angle));
        
        double dutyCycle = MIN_DUTY + (MAX_DUTY - MIN_DUTY) * 
            (angle - minAngle) / (maxAngle - minAngle);
        
        pwm.on(SERVO_FREQ, dutyCycle);
    }
    
    public void setPulseWidth(int micros) {
        double dutyCycle = (micros / 20000.0) * 100;
        pwm.on(SERVO_FREQ, dutyCycle);
    }
    
    public void sweep(int fromAngle, int toAngle, int steps, int delayMs) 
            throws InterruptedException {
        for (int i = 0; i <= steps; i++) {
            int angle = fromAngle + (toAngle - fromAngle) * i / steps;
            setAngle(angle);
            Thread.sleep(delayMs);
        }
    }
    
    public void off() {
        pwm.off();
    }
    
    public static void main(String[] args) throws InterruptedException {
        var pi4j = Pi4J.newAutoContext();
        
        ServoMotor servo = new ServoMotor(pi4j, 18);
        
        System.out.println("舵机控制演示");
        
        System.out.println("转到0度");
        servo.setAngle(0);
        Thread.sleep(1000);
        
        System.out.println("转到90度");
        servo.setAngle(90);
        Thread.sleep(1000);
        
        System.out.println("转到180度");
        servo.setAngle(180);
        Thread.sleep(1000);
        
        System.out.println("来回扫描");
        for (int i = 0; i < 3; i++) {
            servo.sweep(0, 180, 180, 10);
            servo.sweep(180, 0, 180, 10);
        }
        
        servo.off();
        pi4j.shutdown();
    }
}

四、ADC基础概念 #

4.1 ADC概述 #

ADC(Analog-to-Digital Converter,模数转换器)将模拟信号转换为数字信号。

text
┌─────────────────────────────────────────────────────────┐
│                    ADC转换过程                           │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  模拟信号                    数字输出                    │
│                                                         │
│     3.3V ─┬─────────────────── 11111111 (255)          │
│           │                                             │
│     2.5V ─┼─────────────────── 11001100 (204)          │
│           │                                             │
│     1.7V ─┼─────────────────── 10000101 (133)          │
│           │                                             │
│     0.8V ─┼─────────────────── 00111111 (63)           │
│           │                                             │
│     0.0V ─┴─────────────────── 00000000 (0)            │
│                                                         │
│  分辨率 = 参考电压 / (2^n - 1)                           │
│  8位ADC: 3.3V / 255 ≈ 12.9mV                           │
│  10位ADC: 3.3V / 1023 ≈ 3.2mV                          │
│  12位ADC: 3.3V / 4095 ≈ 0.8mV                          │
│                                                         │
└─────────────────────────────────────────────────────────┘

4.2 ADC参数 #

参数 说明
分辨率 数字输出的位数(8/10/12/16位)
采样率 每秒采样次数
参考电压 ADC的满量程电压
精度 实际值与测量值的偏差
通道数 可测量的模拟输入数量

五、ADC编程 #

5.1 MCP3008 ADC(SPI接口) #

java
package com.example.adc;

import com.pi4j.Pi4J;
import com.pi4j.io.spi.Spi;
import com.pi4j.io.spi.SpiMode;

public class MCP3008ADC {

    private static final int NUM_CHANNELS = 8;
    private static final int RESOLUTION = 1024;
    private static final double VREF = 3.3;
    
    private final Spi spi;
    
    public MCP3008ADC(var pi4j, int bus, int chipSelect) {
        this.spi = pi4j.create(Spi.newConfigBuilder(pi4j)
                .id("mcp3008")
                .name("MCP3008 ADC")
                .bus(bus)
                .chipSelect(chipSelect)
                .mode(SpiMode.MODE_0)
                .baud(1000000)
                .build());
    }
    
    public int readRaw(int channel) {
        if (channel < 0 || channel >= NUM_CHANNELS) {
            throw new IllegalArgumentException("Invalid channel: " + channel);
        }
        
        byte[] txData = {
            0x01,
            (byte) ((channel + 8) << 4),
            0x00
        };
        
        byte[] rxData = spi.transfer(txData);
        
        return ((rxData[1] & 0x03) << 8) | (rxData[2] & 0xFF);
    }
    
    public double readVoltage(int channel) {
        return (readRaw(channel) * VREF) / (RESOLUTION - 1);
    }
    
    public double readPercentage(int channel) {
        return (readRaw(channel) * 100.0) / (RESOLUTION - 1);
    }
    
    public static void main(String[] args) throws InterruptedException {
        var pi4j = Pi4J.newAutoContext();
        
        MCP3008ADC adc = new MCP3008ADC(pi4j, 0, 0);
        
        System.out.println("MCP3008 ADC测试");
        System.out.println("参考电压: " + VREF + "V");
        
        while (true) {
            System.out.println("=== 通道读数 ===");
            for (int i = 0; i < NUM_CHANNELS; i++) {
                int raw = adc.readRaw(i);
                double voltage = adc.readVoltage(i);
                System.out.printf("CH%d: %4d (%.3fV)%n", i, raw, voltage);
            }
            Thread.sleep(1000);
        }
    }
}

5.2 ADS1115 ADC(I2C接口) #

java
package com.example.adc;

import com.pi4j.Pi4J;
import com.pi4j.io.i2c.I2C;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class ADS1115ADC {

    private static final int ADDRESS = 0x48;
    
    private static final int REG_CONVERSION = 0x00;
    private static final int REG_CONFIG = 0x01;
    private static final int REG_LO_THRESH = 0x02;
    private static final int REG_HI_THRESH = 0x03;
    
    public enum Gain {
        GAIN_6V(0x0000, 6.144),
        GAIN_4V(0x0200, 4.096),
        GAIN_2V(0x0400, 2.048),
        GAIN_1V(0x0600, 1.024),
        GAIN_0V5(0x0800, 0.512),
        GAIN_0V25(0x0A00, 0.256);
        
        final int value;
        final double range;
        
        Gain(int value, double range) {
            this.value = value;
            this.range = range;
        }
    }
    
    public enum Mux {
        AIN0_AIN1(0x0000),
        AIN0_AIN3(0x1000),
        AIN1_AIN3(0x2000),
        AIN2_AIN3(0x3000),
        AIN0_GND(0x4000),
        AIN1_GND(0x5000),
        AIN2_GND(0x6000),
        AIN3_GND(0x7000);
        
        final int value;
        
        Mux(int value) {
            this.value = value;
        }
    }
    
    public enum Rate {
        SPS_8(0x0000),
        SPS_16(0x0020),
        SPS_32(0x0040),
        SPS_64(0x0060),
        SPS_128(0x0080),
        SPS_250(0x00A0),
        SPS_475(0x00C0),
        SPS_860(0x00E0);
        
        final int value;
        
        Rate(int value) {
            this.value = value;
        }
    }
    
    private final I2C device;
    private Gain gain = Gain.GAIN_4V;
    private Rate rate = Rate.SPS_128;
    
    public ADS1115ADC(var pi4j, int bus) {
        this.device = pi4j.create(I2C.newConfigBuilder(pi4j)
                .id("ads1115")
                .name("ADS1115 ADC")
                .bus(bus)
                .device(ADDRESS)
                .build());
    }
    
    public void setGain(Gain gain) {
        this.gain = gain;
    }
    
    public void setRate(Rate rate) {
        this.rate = rate;
    }
    
    public int readRaw(Mux mux) {
        int config = 0x8000 |
                     mux.value |
                     gain.value |
                     0x0100 |
                     rate.value |
                     0x0003;
        
        byte[] configBytes = ByteBuffer.allocate(2)
                .order(ByteOrder.BIG_ENDIAN)
                .putShort((short) config)
                .array();
        
        device.writeRegisterBuffer(REG_CONFIG, configBytes, 0, 2);
        
        try { Thread.sleep(10); } catch (InterruptedException e) {}
        
        byte[] data = new byte[2];
        device.readRegisterBuffer(REG_CONVERSION, data, 0, 2);
        
        return ByteBuffer.wrap(data)
                .order(ByteOrder.BIG_ENDIAN)
                .getShort();
    }
    
    public double readVoltage(Mux mux) {
        int raw = readRaw(mux);
        return (raw * gain.range) / 32767.0;
    }
    
    public double readSingleEnded(int channel) {
        Mux mux;
        switch (channel) {
            case 0: mux = Mux.AIN0_GND; break;
            case 1: mux = Mux.AIN1_GND; break;
            case 2: mux = Mux.AIN2_GND; break;
            case 3: mux = Mux.AIN3_GND; break;
            default: throw new IllegalArgumentException("Invalid channel");
        }
        return readVoltage(mux);
    }
    
    public double readDifferential(int channel) {
        Mux mux;
        switch (channel) {
            case 0: mux = Mux.AIN0_AIN1; break;
            case 1: mux = Mux.AIN2_AIN3; break;
            default: throw new IllegalArgumentException("Invalid channel");
        }
        return readVoltage(mux);
    }
    
    public static void main(String[] args) throws InterruptedException {
        var pi4j = Pi4J.newAutoContext();
        
        ADS1115ADC adc = new ADS1115ADC(pi4j, 1);
        adc.setGain(Gain.GAIN_4V);
        
        System.out.println("ADS1115 ADC测试");
        
        while (true) {
            System.out.println("=== 单端输入 ===");
            for (int i = 0; i < 4; i++) {
                double voltage = adc.readSingleEnded(i);
                System.out.printf("AIN%d: %.4fV%n", i, voltage);
            }
            Thread.sleep(1000);
        }
    }
}

六、传感器应用 #

6.1 光敏电阻 #

java
package com.example.adc.sensor;

import com.example.adc.MCP3008ADC;

public class LightSensor {

    private final MCP3008ADC adc;
    private final int channel;
    private final double vref;
    private final double fixedResistor;
    
    public LightSensor(MCP3008ADC adc, int channel, double vref, double fixedResistor) {
        this.adc = adc;
        this.channel = channel;
        this.vref = vref;
        this.fixedResistor = fixedResistor;
    }
    
    public double readVoltage() {
        return adc.readVoltage(channel);
    }
    
    public double readResistance() {
        double vOut = readVoltage();
        return fixedResistor * (vref - vOut) / vOut;
    }
    
    public double readLux() {
        double resistance = readResistance();
        return 10000.0 / Math.pow(resistance / 10000.0, 1.4);
    }
    
    public static void main(String[] args) throws InterruptedException {
        var pi4j = Pi4J.newAutoContext();
        
        MCP3008ADC adc = new MCP3008ADC(pi4j, 0, 0);
        LightSensor light = new LightSensor(adc, 0, 3.3, 10000);
        
        System.out.println("光敏传感器测试");
        
        while (true) {
            double voltage = light.readVoltage();
            double lux = light.readLux();
            
            System.out.printf("电压: %.3fV, 光照: %.1f lux%n", voltage, lux);
            Thread.sleep(500);
        }
    }
}

6.2 电位器 #

java
package com.example.adc.sensor;

import com.example.adc.MCP3008ADC;

public class Potentiometer {

    private final MCP3008ADC adc;
    private final int channel;
    private final int minValue;
    private final int maxValue;
    
    public Potentiometer(MCP3008ADC adc, int channel, int minValue, int maxValue) {
        this.adc = adc;
        this.channel = channel;
        this.minValue = minValue;
        this.maxValue = maxValue;
    }
    
    public int readValue() {
        int raw = adc.readRaw(channel);
        return minValue + (maxValue - minValue) * raw / 1023;
    }
    
    public double readPercentage() {
        return adc.readPercentage(channel);
    }
    
    public static void main(String[] args) throws InterruptedException {
        var pi4j = Pi4J.newAutoContext();
        
        MCP3008ADC adc = new MCP3008ADC(pi4j, 0, 0);
        Potentiometer pot = new Potentiometer(adc, 0, 0, 100);
        
        System.out.println("电位器测试");
        
        while (true) {
            int value = pot.readValue();
            double percent = pot.readPercentage();
            
            System.out.printf("值: %d, 百分比: %.1f%%%n", value, percent);
            Thread.sleep(200);
        }
    }
}

七、PWM与ADC综合应用 #

7.1 自动调光系统 #

java
package com.example.pwm.adc;

import com.example.adc.MCP3008ADC;
import com.pi4j.Pi4J;
import com.pi4j.io.pwm.Pwm;
import com.pi4j.io.pwm.PwmType;

public class AutoDimmingSystem {

    private static final int LIGHT_SENSOR_CHANNEL = 0;
    private static final int PWM_PIN = 18;
    private static final int PWM_FREQ = 1000;
    
    private final MCP3008ADC adc;
    private final Pwm pwm;
    private final int targetLux;
    
    public AutoDimmingSystem(var pi4j, int targetLux) {
        this.adc = new MCP3008ADC(pi4j, 0, 0);
        this.targetLux = targetLux;
        
        this.pwm = pi4j.create(Pwm.newConfigBuilder(pi4j)
                .id("led-pwm")
                .name("LED PWM")
                .address(PWM_PIN)
                .pwmType(PwmType.HARDWARE)
                .frequency(PWM_FREQ)
                .dutyCycle(0)
                .build());
    }
    
    public void run() throws InterruptedException {
        System.out.println("自动调光系统启动");
        System.out.println("目标光照: " + targetLux + " lux");
        
        double kp = 0.5;
        double ki = 0.01;
        double kd = 0.1;
        
        double integral = 0;
        double lastError = 0;
        
        while (true) {
            int raw = adc.readRaw(LIGHT_SENSOR_CHANNEL);
            double currentLux = rawToLux(raw);
            
            double error = targetLux - currentLux;
            integral += error;
            double derivative = error - lastError;
            
            double output = kp * error + ki * integral + kd * derivative;
            int dutyCycle = (int) Math.max(0, Math.min(100, 50 + output));
            
            pwm.on(PWM_FREQ, dutyCycle);
            
            System.out.printf("当前: %.1f lux, 目标: %d lux, 占空比: %d%%%n",
                currentLux, targetLux, dutyCycle);
            
            lastError = error;
            Thread.sleep(100);
        }
    }
    
    private double rawToLux(int raw) {
        return raw * 100.0 / 1023.0;
    }
    
    public static void main(String[] args) throws InterruptedException {
        var pi4j = Pi4J.newAutoContext();
        
        AutoDimmingSystem system = new AutoDimmingSystem(pi4j, 50);
        system.run();
    }
}

八、总结 #

PWM与ADC要点:

  1. PWM控制:通过占空比控制平均输出电压
  2. 硬件PWM:优先使用硬件PWM获得稳定波形
  3. ADC采样:理解分辨率和参考电压的关系
  4. 传感器接口:掌握常见模拟传感器的使用方法
  5. PID控制:结合PWM和ADC实现自动控制

下一章我们将学习传感器数据采集,深入了解各类传感器的使用方法。

最后更新:2026-03-27