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要点:
- PWM控制:通过占空比控制平均输出电压
- 硬件PWM:优先使用硬件PWM获得稳定波形
- ADC采样:理解分辨率和参考电压的关系
- 传感器接口:掌握常见模拟传感器的使用方法
- PID控制:结合PWM和ADC实现自动控制
下一章我们将学习传感器数据采集,深入了解各类传感器的使用方法。
最后更新:2026-03-27