7. (Important) Chapter ADC

We have learned how to control the brightness of an LED through PWM and that PWM is not a real analog signal. In this chapter, we will learn how to read analog values via an ADC Module and convert these analog values into digital.

7.1. Project Read the Voltage of Potentiometer

In this project, we will use the ADC function of an ADC Module to read the voltage value of a potentiometer.

7.1.1. Component List

  1. Raspberry Pi (with 40 GPIO) x1

  2. GPIO Extension Board & Ribbon Cable x1

  3. Breadboard x1

Rotary potentiometer x1

Rotary-potentiometer

Resistor 10kΩ x2

Resistor-10kΩ

ADC module x1

ADC-module-2

Jumper Wire

jumper-wire

7.1.2. Circuit knowledge

7.1.2.1. ADC

An ADC is an electronic integrated circuit used to convert analog signals such as voltages to digital or binary form consisting of 1s and 0s. The range of our ADC module is 8 bits, that means the resolution is 2^8=256, so that its range (at 3.3V) will be divided equally to 256 parts. Any analog value can be mapped to one digital value using the resolution of the converter. So the more bits the ADC has, the denser the partition of analog will be and the greater the precision of the resulting conversion.

../../../_images/ADC.png

Subsection 1: the analog in range of 0V-3.3/256 V corresponds to digital 0;

Subsection 2: the analog in range of 3.3 /256 V-2*3.3 /256V corresponds to digital 1;

The resultant analog signal will be divided accordingly.

7.1.2.2. DAC

The reversing this process requires a DAC, Digital-to-Analog Converter.

The digital I/O port can output high level and low level (0 or 1), but cannot output an intermediate voltage value.

This is where a DAC is useful. The DAC module PCF8591 has a DAC output pin with 8-bit accuracy, which can divide VDD (here is 3.3V) into 28 = 256 parts. For example, when the digital quantity is 1, the output voltage value is 3.3/256 *1 V, and when the digital quantity is 128, the output voltage value is 3.3/256 *128=1.65V, the higher the accuracy of DAC, the higher the accuracy of output voltage value will be.

7.1.3. Component knowledge

7.1.3.1. Potentiometer

Potentiometer is a resistive element with three Terminal parts. Unlike the resistors that we have used thus far in our project which have a fixed resistance value, the resistance value of a potentiometer can be adjusted. A potentiometer is often made up by a resistive substance (a wire or carbon element) and movable contact brush. When the brush moves along the resistor element, there will be a change in the resistance of the potentiometer’s output side (3) (or change in the voltage of the circuit that is a part). The illustration below represents a linear sliding potentiometer and its electronic symbol on the right.

../../../_images/1-32.png ../../../_images/1-32-2.png

Between potentiometer pin 1 and pin 2 is the resistive element (a resistance wire or carbon) and pin 3 is connected to the brush that makes contact with the resistive element. In our illustration, when the brush moves from pin 1 to pin 2, the resistance value between pin 1 and pin 3 will increase linearly (until it reaches the highest value of the resistive element) and at the same time the resistance between pin 2 and pin 3 will decrease linearly and conversely down to zero. At the midpoint of the slider the measured resistance values between pin 1 and 3 and between pin 2 and 3 will be the same.

In a circuit, both sides of resistive element are often connected to the positive and negative electrodes of power. When you slide the brush “pin 3”, you can get variable voltage within the range of the power supply.

../../../_images/1-32-fritizing.png

7.1.3.2. Rotary potentiometer

Rotary potentiometers and linear potentiometers have the same function; the only difference being the physical action being a rotational rather than a sliding movement.

../../../_images/Rotary-potentiometer-turn.png

7.1.3.3. ADS7830

The ADS7830 is a single-supply, low-power, 8-bit data acquisition device that features a serial I2C interface and an 8-channel multiplexer. The following table is the pin definition diagram of ADS7830.

SYMBOL

PIN

DESCRIPTION

TOP VIEW

CH0

1

Analog input channels (A/D converter)

ADS7830-top

CH1

2

CH2

3

CH3

4

CH4

5

CH5

6

CH6

7

CH7

8

GND

9

Ground

REF in/out

10

Internal +2.5V Reference,External Reference Input

COM

11

Common to Analog Input Channel

A0

12

Hardware address

A1

13

SCL

14

Serial Clock

SDA

15

Serial Sata

+VDD

16

Power Supply, 3.3V Nominal

7.1.3.4. I2C communication

I2C (Inter-Integrated Circuit) has a two-wire serial communication mode, which can be used to connect a micro-controller and its peripheral equipment. Devices using I2C communications must be connected to the serial data line (SDA), and serial clock line (SCL) (called I2C bus). Each device has a unique address which can be used as a transmitter or receiver to communicate with devices connected via the bus.

7.1.4. Circuit

Schematic diagram

ADS7830-Schematic

Hardware connection. If you need any support,please feel free to contact us via:

support@freenove.com

ADS7830-fritizing

7.1.5. Configure I2C and Install Smbus

7.1.5.1. Enable I2C

The I2C interface in Raspberry Pi is disabled by default. You will need to open it manually and enable the I2C interface as follows:

Type command in the Terminal:

$ sudo raspi-config

Then open the following dialog box:

../../../_images/java07_00.png

Choose “Interfacing Options” then “I5 I2C” then “Yes” and then “Finish” in this order and restart your RPi. The I2C module will then be started.

../../../_images/java07_01.png

Type a command to check whether the I2C module is started:

$ lsmod | grep i2c

If the I2C module has been started, the following content will be shown.

Different models of Raspberry Pi display different contents depending on the CPU installed:

../../../_images/Chapter07_00.png

I2C device address detection:

$ i2cdetect -y 1

When you are using the ADS7830 Module, the result should look like this:

../../../_images/java07_02.png

Here, 48 (HEX) is the I2C address of ADC Module (ADS7830).

7.1.6. Sketch

In this chapter, we will learn the combined usage of ADC and potentiometer.

7.1.6.1. Sketch_ADC

First, enter where the project is located:

$ cd ~/Freenove_Kit/Pi4j/Sketches/Sketch_07_1_ADC
../../../_images/java_ADC.png

Enter the command to run the code.

$ jbang ADC.java
../../../_images/java_ADC_run.png

When the code is running, rotate the potentiometer marked below.

../../../_images/ADS7830-fritizing.png

You can see that the ADC values change with the rotation of the potentiometer. The value 0 means that the potentiometer’s voltage read by ADC is 0V, 255 indicates that the voltage is 5V.

../../../_images/java_ADC_mes.png

Press Ctrl+C to exit the code.

../../../_images/java_ADC_exit.png

You can open the code with Geany with the following command to view and edit it.

$ geany ADC.java

Click the icon to run the code.

../../../_images/java_ADC_code.png

If the code fails to run, please check Geany Configuration.

The following is program code:

 1///usr/bin/env jbang "$0" "$@" ; exit $?  
 2
 3//DEPS org.slf4j:slf4j-api:2.0.12  
 4//DEPS org.slf4j:slf4j-simple:2.0.12  
 5//DEPS com.pi4j:pi4j-core:2.6.0  
 6//DEPS com.pi4j:pi4j-plugin-raspberrypi:2.6.0  
 7//DEPS com.pi4j:pi4j-plugin-gpiod:2.6.0  
 8//DEPS com.pi4j:pi4j-plugin-linuxfs:2.6.0  
 9
10import com.pi4j.Pi4J;
11import com.pi4j.context.Context;
12import com.pi4j.io.i2c.I2C;
13import com.pi4j.io.i2c.I2CConfig;
14import com.pi4j.io.i2c.I2CProvider;
15import com.pi4j.util.Console;
16
17class ADCDevice {
18    private final I2C adcChip;
19    private final int adcChipAddr;
20
21    public ADCDevice(Context pi4j, I2CProvider provider, int adcChipAddr) throws Exception {
22        this.adcChipAddr = adcChipAddr;
23        I2CConfig i2cConfig = I2C.newConfigBuilder(pi4j).id("ADCDevice").bus(1).device(adcChipAddr).build();
24        this.adcChip = provider.create(i2cConfig);
25    }
26
27    public boolean detectI2C() throws Exception {
28        try {
29            adcChip.write(0);
30            byte[] data = new byte[1];
31            int bytesRead = adcChip.read(data, 0, 1);
32            return bytesRead == 1;
33        } catch (Exception e) {
34            return false;
35        }
36    }
37
38    public int analogRead(int chn) {
39        byte command = (byte) (0x84 | (((chn << 2 | chn >> 1) & 0x07) << 4));
40        adcChip.write(command);
41        byte[] data = new byte[1];
42        int bytesRead = adcChip.read(data, 0, 1);
43        if (bytesRead == 1) {
44            int adcValue = data[0] & 0xFF;
45            return adcValue;
46        } else {
47            return -1;
48        }
49    }
50}
51
52public class ADC {
53
54    public static void myPrintln(String format, Object... args) {
55        Console console = new Console();
56        console.println(String.format("\u001B[32m" + format + "\u001B[0m", args));
57    }
58
59    public static void main(String[] args) throws Exception {
60        Context pi4j = Pi4J.newAutoContext();
61        I2CProvider i2CProvider = pi4j.provider("linuxfs-i2c");
62        try {
63            int ADC_CHIP_ADDR = 0x4B;
64            ADCDevice adcDevice = new ADCDevice(pi4j, i2CProvider, ADC_CHIP_ADDR);
65            if (adcDevice.detectI2C()) {
66                int ADC_CHANNEL = 0;
67                while (true) {
68                    int adcValue = adcDevice.analogRead(ADC_CHANNEL);
69                    if (adcValue != -1) {
70                        myPrintln("ADC Channel %d Value:%d", ADC_CHANNEL, adcValue);
71                    } 
72                    else {
73                        myPrintln("Failed to read data from ADC.");
74                    }
75                    Thread.sleep(100);
76                }
77            } 
78            else {
79                myPrintln("ADS7830 device not detected at address 0x" + Integer.toHexString(ADC_CHIP_ADDR));
80            }
81        } 
82        finally {
83            pi4j.shutdown();
84        }
85    }
86}

Dependency declaration, these libraries will be automatically downloaded by jbang at runtime and added to the classpath.

1//DEPS org.slf4j:slf4j-api:2.0.12  
2//DEPS org.slf4j:slf4j-simple:2.0.12  
3//DEPS com.pi4j:pi4j-core:2.6.0  
4//DEPS com.pi4j:pi4j-plugin-raspberrypi:2.6.0  
5//DEPS com.pi4j:pi4j-plugin-gpiod:2.6.0  
6//DEPS com.pi4j:pi4j-plugin-linuxfs:2.6.0  

Import I2C library. In this project, we use I2C to read the channel value of ADS7830.

1import com.pi4j.Pi4J;
2import com.pi4j.context.Context;
3import com.pi4j.io.i2c.I2C;
4import com.pi4j.io.i2c.I2CConfig;
5import com.pi4j.io.i2c.I2CProvider;
6import com.pi4j.util.Console;

Constructor of ADCDevice class, which is used to initialize I2C bus to facilitate later reading and writing ADS7830 chip.

1public ADCDevice(Context pi4j, I2CProvider provider, int adcChipAddr) throws Exception {
2    this.adcChipAddr = adcChipAddr;
3    I2CConfig i2cConfig = I2C.newConfigBuilder(pi4j).id("ADCDevice").bus(1).device(adcChipAddr).build();
4    this.adcChip = provider.create(i2cConfig);
5}

Write a byte to the target chip, and then read the data. If the data can be read, it means the target chip exists and communication is successful. If an I2C exception is detected, it means the target chip does not exist.

 1public boolean detectI2C() throws Exception {
 2    try {
 3        adcChip.write(0);
 4        byte[] data = new byte[1];
 5        int bytesRead = adcChip.read(data, 0, 1);
 6        return bytesRead == 1;
 7    } catch (Exception e) {
 8        return false;
 9    }
10}

Write the read command to the ADS7830 and read the corresponding ADC value. It is returned by the return value.

 1public int analogRead(int chn) {
 2    byte command = (byte) (0x84 | (((chn << 2 | chn >> 1) & 0x07) << 4));
 3    adcChip.write(command);
 4    byte[] data = new byte[1];
 5    int bytesRead = adcChip.read(data, 0, 1);
 6    if (bytesRead == 1) {
 7        int adcValue = data[0] & 0xFF;
 8        return adcValue;
 9    } else {
10        return -1;
11    }
12}

Create a pi4j context to get the Raspberry PI i2c interface.

1Context pi4j = Pi4J.newAutoContext();
2I2CProvider i2CProvider = pi4j.provider("linuxfs-i2c");

The I2C address of the ADS7830 is 0x48.

Create an ADCDevice class, associate it with the Raspberry PI I2C interface, and assign a value to the adcDevice.

1int ADC_CHIP_ADDR = 0x4B;
2ADCDevice adcDevice = new ADCDevice(pi4j, i2CProvider, ADC_CHIP_ADDR);

Check whether the chip can communicate normally. If the communication is successful, read channel 0 of the ADS7830 chip and print it out in the terminal.

 1if (adcDevice.detectI2C()) {
 2    int ADC_CHANNEL = 0;
 3    while (true) {
 4        int adcValue = adcDevice.analogRead(ADC_CHANNEL);
 5        if (adcValue != -1) {
 6            myPrintln("ADC Channel %d Value:%d", ADC_CHANNEL, adcValue);
 7        } 
 8        else {
 9            myPrintln("Failed to read data from ADC.");
10        }
11        Thread.sleep(100);
12    }
13} 

If communication with the chip fails, a prompt message is printed on the terminal.

1else {
2    myPrintln("ADS7830 device not detected at address 0x" + Integer.toHexString(ADC_CHIP_ADDR));
3}

When the code finishes running, close the Pi4J context.

1finally {
2    pi4j.shutdown();
3}

7.2. Project Soft Light

7.2.1. Component List

  1. Raspberry Pi (with 40 GPIO) x1

  2. GPIO Extension Board & Ribbon Cable x1

  3. Breadboard x1

Rotary potentiometer x1

Rotary-potentiometer

Resistor 220Ω x1

res-220R

Resistor 10kΩ x2

Resistor-10kΩ

ADC module x1 (Only one)

ADC-module-2

LED x1

red-led

Jumper Wire M/M x17

jumper-wire

7.2.2. Circuit

Schematic diagram

ADS7830-Schematic-2

Hardware connection. If you need any support,please feel free to contact us via:

support@freenove.com

ADS7830-fritizing-2

7.2.3. Sketch

In this project, we learn how to control the brightness of LED with the potentiometer.

7.2.3.1. Sketch_Softlight

First, enter where the project is located:

$ cd ~/Freenove_Kit/Pi4j/Sketches/Sketch_07_2_Softlight
../../../_images/java_softLight.png

Enter the command to run code.

$ jbang Softlight.java
../../../_images/java_softLight_run.png

When the code is running, turn the potentiometer marked below and you can see the brightness of the LED change.

../../../_images/ADS7830-fritizing-2.png

On the Terminal, you can see the printed ADC values and the calculated voltage values.

../../../_images/java_softLight_mes.png

Press Ctrl+C to exit the program.

You can open the code with Geany with the following command to view and edit it.

$ geany Softlight.java

Click the icon to run the code.

../../../_images/java_softLight_code.png

If the code fails to run, please check Geany Configuration.

The following is program code:

  1///usr/bin/env jbang "$0" "$@" ; exit $?  
  2  
  3//DEPS org.slf4j:slf4j-api:2.0.12  
  4//DEPS org.slf4j:slf4j-simple:2.0.12  
  5//DEPS com.pi4j:pi4j-core:2.6.0  
  6//DEPS com.pi4j:pi4j-plugin-raspberrypi:2.6.0  
  7//DEPS com.pi4j:pi4j-plugin-gpiod:2.6.0  
  8//DEPS com.pi4j:pi4j-plugin-linuxfs:2.6.0  
  9
 10import com.pi4j.Pi4J;  
 11import com.pi4j.context.Context;  
 12import com.pi4j.io.i2c.I2C;  
 13import com.pi4j.io.i2c.I2CConfig;  
 14import com.pi4j.io.i2c.I2CProvider;  
 15import com.pi4j.util.Console;  
 16import com.pi4j.io.gpio.digital.DigitalOutput;    
 17import java.util.HashMap;  
 18import java.util.Map;  
 19
 20class PWMController implements Runnable {  
 21    private DigitalOutput pwm;  
 22    private int pwmFrequency;
 23    private double pwmDutyCycle;
 24    private boolean running = true;  
 25    private long period;  
 26    private long highTime;  
 27    private long lowTime;  
 28    
 29    public PWMController(DigitalOutput pwm) {  
 30        this.pwm = pwm;  
 31        this.pwmFrequency = 1000;
 32        this.pwmDutyCycle = 0.5;
 33        this.period = (int) (1000000 / pwmFrequency);  
 34        this.highTime = (int) (period * pwmDutyCycle);  
 35        this.lowTime = (int) (period - highTime);  
 36    }  
 37  
 38    @Override  
 39    public void run() {  
 40        while (running) {    
 41            if(highTime!=0){
 42                pwm.high();  
 43                delayUs(highTime); 
 44            }           
 45            if(lowTime!=0){
 46                pwm.low();  
 47                delayUs(lowTime);  
 48            }
 49        }  
 50    }  
 51  
 52    public void setPwmFrequency(int frequency) {  
 53        if(frequency!=0){
 54            this.pwmFrequency = frequency;
 55            this.period = (int) (1000000 / pwmFrequency);  
 56            this.highTime = (int) (period * pwmDutyCycle);  
 57            this.lowTime = (int) (period - highTime); 
 58        }
 59        else{
 60            this.pwmFrequency = 0;
 61            this.period = (int) (1000);  
 62            this.highTime = (int) (0);  
 63            this.lowTime = (int) (period - highTime); 
 64        }
 65    }  
 66    
 67    public void setPwmDutyCycle(double dutyCycle) {  
 68        this.pwmDutyCycle = dutyCycle;
 69        this.highTime = (int) (period * pwmDutyCycle);  
 70        this.lowTime = (int) (period - highTime); 
 71    } 
 72
 73    private void delayUs(long us) {  
 74        long startTime = System.nanoTime();  
 75        long endTime = startTime + (us * 1000);  
 76        while (System.nanoTime() < endTime) {  
 77        }  
 78    }  
 79  
 80    public void requestStop() {  
 81        running = false;  
 82    }  
 83}
 84
 85class ADCDevice {  
 86    private final I2C adcChip;  
 87    private final int adcChipAddr;  
 88  
 89    public ADCDevice(Context pi4j, I2CProvider provider, int adcChipAddr) throws Exception {  
 90        this.adcChipAddr = adcChipAddr;  
 91        I2CConfig i2cConfig = I2C.newConfigBuilder(pi4j).id("ADCDevice").bus(1).device(adcChipAddr).build();  
 92        this.adcChip = provider.create(i2cConfig);  
 93    }  
 94  
 95    public boolean detectI2C() throws Exception {  
 96        try {  
 97            adcChip.write(0);  
 98            byte[] data = new byte[1];  
 99            int bytesRead = adcChip.read(data, 0, 1);  
100            return bytesRead == 1;  
101        } catch (Exception e) {  
102            return false;  
103        }  
104    }  
105  
106    public int analogRead(int chn) {  
107        byte command = (byte) (0x84 | (((chn << 2 | chn >> 1) & 0x07) << 4));  
108        adcChip.write(command);  
109        byte[] data = new byte[1];  
110        int bytesRead = adcChip.read(data, 0, 1);  
111        if (bytesRead == 1) {  
112            int adcValue = data[0] & 0xFF;  
113            return adcValue;  
114        } else {  
115            return -1;  
116        }  
117    }  
118}
119
120public class Softlight{
121    private static int   LED_PIN = 17;
122    private static int   ADC_CHIP_ADDR = 0x4B; 
123    private static int   ADC_CHANNEL = 0;
124    
125    private static final Context pi4j = Pi4J.newAutoContext();  
126    private static final Map<Integer, PWMController> pwmControllers = new HashMap<>();  
127    
128    public static void setPwmConfig(int pin) throws Exception {  
129        DigitalOutput pwm = pi4j.dout().create(pin);  
130        PWMController pwmController = new PWMController(pwm);  
131        Thread pwmThread = new Thread(pwmController, "PWM Controller " + pin);  
132        pwmControllers.put(pin, pwmController);  
133        pwmThread.start();  
134        Runtime.getRuntime().addShutdownHook(new Thread(() -> {  
135            pwmController.requestStop();  
136            try {  
137                pwmThread.join();  
138            } catch (InterruptedException e) {  
139                Thread.currentThread().interrupt();  
140            }  
141        }));  
142    } 
143    
144    public static void myPrintln(String format, Object... args) {    
145        Console console = new Console();  
146        console.println(String.format("\u001B[32m" + format + "\u001B[0m", args));   
147    }
148    
149    public static void main(String[] args) throws Exception {  
150        Context pi4j = Pi4J.newAutoContext();  
151        I2CProvider i2CProvider = pi4j.provider("linuxfs-i2c");
152        setPwmConfig(LED_PIN);  
153        PWMController led = pwmControllers.get(LED_PIN); 
154        
155        try {  
156            ADCDevice adc = new ADCDevice(pi4j, i2CProvider, ADC_CHIP_ADDR);  
157            if (adc.detectI2C()) {  
158                while (true) {  
159                    int adcValue = adc.analogRead(ADC_CHANNEL);  
160                    if (adcValue != -1) {  
161                        led.setPwmDutyCycle(((double)adcValue/255.0));
162                        double voltage = (double)adcValue / 255.0 * 5.0;
163                        myPrintln("ADC value:%d, Voltage:%.2fV", adcValue, voltage); 
164                    } else {  
165                        myPrintln("Failed to read data from ADC.");  
166                    }  
167                    Thread.sleep(100);  
168                }  
169            } else {  
170                myPrintln("ADS7830 device not detected at address 0x" + Integer.toHexString(ADC_CHIP_ADDR));  
171            }  
172        } finally {  
173            pi4j.shutdown();  
174        }  
175    }  
176}

The ADC value of the potentiometer is obtained every 100 milliseconds and printed on the terminal. Meanwhile, the ADC value is converted into the duty cycle value of the LED to control the brightness of the LED.

 1while (true) {  
 2    int adcValue = adc.analogRead(ADC_CHANNEL);  
 3    if (adcValue != -1) {  
 4        led.setPwmDutyCycle(((double)adcValue/255.0));
 5        double voltage = (double)adcValue / 255.0 * 5.0;
 6        myPrintln("ADC value:%d, Voltage:%.2fV", adcValue, voltage); 
 7    } else {  
 8        myPrintln("Failed to read data from ADC.");  
 9    }  
10    Thread.sleep(100);  
11}  

7.3. Project Colorful Light

In this project, 3 potentiometers are used to control the RGB LED and in principle, it is the same as with the ‘Soft Light’ project. Namely, read the voltage value of the potentiometer and then convert it to PWM used to control LED brightness. Difference is that the previous soft light project needed only one LED while this one required (3) RGB LEDs.

7.3.1. Component List

  1. Raspberry Pi (with 40 GPIO) x1

  2. GPIO Extension Board & Ribbon Cable x1

  3. Breadboard x1

Rotary potentiometer x1

Rotary-potentiometer

Resistor 220Ω x1

res-220R

Resistor 10kΩ x2

Resistor-10kΩ

ADC module x1 (Only one)

ADC-module-2

RGB LED x1

red-led

Jumper Wire M/M x17

jumper-wire

7.3.2. Circuit

Schematic diagram

ADS7830-Schematic-3

Hardware connection. If you need any support,please feel free to contact us via:

support@freenove.com

ADS7830-fritizing-3

If circuit above doesn’t work, please try following wiring.

ADS7830-fritizing-4

7.3.3. Sketch

In this project, we learn to use the potentiometer to control the color and brightness of the RGB LED.

7.3.4. Sketch_ColorfulSoftlight

First, enter where the project is located:

$ cd ~/Freenove_Kit/Pi4j/Sketches/Sketch_07_3_ColorfulSoftlight
../../../_images/java_color.png

Enter the command to run the code.

$ jbang ColorfulSoftlight.java
../../../_images/java_color_run.png

When the code is running, rotate the three potentiometers marked below, you will see the RGB LED’s color and brightness change.

../../../_images/ADS7830-fritizing-3.png

The ADC value is printed on the terminal.

../../../_images/java_color_mes.png

Press Ctrl+C to exit the program.

You can open the code with Geany with the following command to view and edit it.

$ geany ColorfulSoftlight.java

Click the icon to run the code.

../../../_images/java_color_code.png

If the code fails to run, please check Geany Configuration.

The following is program code:

  1///usr/bin/env jbang "$0" "$@" ; exit $?  
  2
  3//DEPS org.slf4j:slf4j-api:2.0.12  
  4//DEPS org.slf4j:slf4j-simple:2.0.12  
  5//DEPS com.pi4j:pi4j-core:2.6.0  
  6//DEPS com.pi4j:pi4j-plugin-raspberrypi:2.6.0  
  7//DEPS com.pi4j:pi4j-plugin-gpiod:2.6.0  
  8//DEPS com.pi4j:pi4j-plugin-linuxfs:2.6.0  
  9
 10import com.pi4j.Pi4J;  
 11import com.pi4j.context.Context;  
 12import com.pi4j.io.i2c.I2C;  
 13import com.pi4j.io.i2c.I2CConfig;  
 14import com.pi4j.io.i2c.I2CProvider;  
 15import com.pi4j.util.Console;  
 16import com.pi4j.io.gpio.digital.DigitalOutput;    
 17import java.util.HashMap;  
 18import java.util.Map;  
 19
 20class PWMController implements Runnable {  
 21    private DigitalOutput pwm;  
 22    private int pwmFrequency;
 23    private double pwmDutyCycle;
 24    private boolean running = true;  
 25    private long period;  
 26    private long highTime;  
 27    private long lowTime;  
 28
 29    public PWMController(DigitalOutput pwm) {  
 30        this.pwm = pwm;  
 31        this.pwmFrequency = 1000;
 32        this.pwmDutyCycle = 0.5;
 33        this.period = (int) (1000000 / pwmFrequency);  
 34        this.highTime = (int) (period * pwmDutyCycle);  
 35        this.lowTime = (int) (period - highTime);  
 36    }  
 37
 38    @Override  
 39    public void run() {  
 40        while (running) {    
 41            if(highTime!=0){
 42                pwm.high();  
 43                delayUs(highTime); 
 44            }           
 45            if(lowTime!=0){
 46                pwm.low();  
 47                delayUs(lowTime);  
 48            }
 49        }  
 50    }  
 51
 52    public void setPwmFrequency(int frequency) {  
 53        if(frequency!=0){
 54            this.pwmFrequency = frequency;
 55            this.period = (int) (1000000 / pwmFrequency);  
 56            this.highTime = (int) (period * pwmDutyCycle);  
 57            this.lowTime = (int) (period - highTime); 
 58        }
 59        else{
 60            this.pwmFrequency = 0;
 61            this.period = (int) (1000);  
 62            this.highTime = (int) (0);  
 63            this.lowTime = (int) (period - highTime); 
 64        }
 65    }  
 66
 67    public void setPwmDutyCycle(double dutyCycle) {  
 68        this.pwmDutyCycle = dutyCycle;
 69        this.highTime = (int) (period * pwmDutyCycle);  
 70        this.lowTime = (int) (period - highTime); 
 71    } 
 72
 73    private void delayUs(long us) {  
 74        long startTime = System.nanoTime();  
 75        long endTime = startTime + (us * 1000);  
 76        while (System.nanoTime() < endTime) {  
 77        }  
 78    }  
 79
 80    public void requestStop() {  
 81        running = false;  
 82    }  
 83}
 84
 85class ADCDevice {  
 86    private final I2C adcChip;  
 87    private final int adcChipAddr;  
 88
 89    public ADCDevice(Context pi4j, I2CProvider provider, int adcChipAddr) throws Exception {  
 90        this.adcChipAddr = adcChipAddr;  
 91        I2CConfig i2cConfig = I2C.newConfigBuilder(pi4j).id("ADCDevice").bus(1).device(adcChipAddr).build();  
 92        this.adcChip = provider.create(i2cConfig);  
 93    }  
 94
 95    public boolean detectI2C() throws Exception {  
 96        try {  
 97            adcChip.write(0);  
 98            byte[] data = new byte[1];  
 99            int bytesRead = adcChip.read(data, 0, 1);  
100            return bytesRead == 1;  
101        } catch (Exception e) {  
102            return false;  
103        }  
104    }  
105
106    public int analogRead(int chn) {  
107        byte command = (byte) (0x84 | (((chn << 2 | chn >> 1) & 0x07) << 4));  
108        adcChip.write(command);  
109        byte[] data = new byte[1];  
110        int bytesRead = adcChip.read(data, 0, 1);  
111        if (bytesRead == 1) {  
112            int adcValue = data[0] & 0xFF;  
113            return adcValue;  
114        } else {  
115            return -1;  
116        }  
117    }  
118}
119
120public class ColorfulSoftlight{
121    private static int   LED_PIN = 17;
122    private static int   ADC_CHIP_ADDR = 0x4B; 
123    private static final Context pi4j = Pi4J.newAutoContext();  
124    private static final Map<Integer, PWMController> pwmControllers = new HashMap<>();  
125
126    public static void setPwmConfig(int pin) throws Exception {  
127        DigitalOutput pwm = pi4j.dout().create(pin);  
128        PWMController pwmController = new PWMController(pwm);  
129        Thread pwmThread = new Thread(pwmController, "PWM Controller " + pin);  
130        pwmControllers.put(pin, pwmController);  
131        pwmThread.start();  
132        Runtime.getRuntime().addShutdownHook(new Thread(() -> {  
133            pwmController.requestStop();  
134            try {  
135                pwmThread.join();  
136            } catch (InterruptedException e) {  
137                Thread.currentThread().interrupt();  
138            }  
139        }));  
140    } 
141
142    public static void myPrintln(String format, Object... args) {    
143        Console console = new Console();  
144        console.println(String.format("\u001B[32m" + format + "\u001B[0m", args));   
145    }
146
147    public static void main(String[] args) throws Exception {  
148        Context pi4j = Pi4J.newAutoContext();  
149        I2CProvider i2CProvider = pi4j.provider("linuxfs-i2c");
150
151        int[] ADC_CHN =  {0, 1, 2}; 
152        int[] LED_PINS = {17, 27, 22}; 
153        for (int pin : LED_PINS) {  
154            setPwmConfig(pin);  
155        } 
156        PWMController red_led = pwmControllers.get(LED_PINS[0]); 
157        PWMController green_led = pwmControllers.get(LED_PINS[1]); 
158        PWMController blue_led = pwmControllers.get(LED_PINS[2]); 
159
160        try {  
161            ADCDevice adc = new ADCDevice(pi4j, i2CProvider, ADC_CHIP_ADDR);  
162            if (adc.detectI2C()) {  
163                while (true) {  
164                    int val_Red = adc.analogRead(ADC_CHN[0]);  
165                    int val_Green = adc.analogRead(ADC_CHN[1]); 
166                    int val_Blue = adc.analogRead(ADC_CHN[2]); 
167
168                    red_led.setPwmDutyCycle(1-(double)(val_Red/255.0));
169                    green_led.setPwmDutyCycle(1-(double)(val_Green/255.0));
170                    blue_led.setPwmDutyCycle(1-(double)(val_Blue/255.0));
171
172                    myPrintln("ADC value val_Red:%d, val_Green:%d, val_Blue:%d", val_Red, val_Green, val_Blue);
173                    Thread.sleep(100);  
174                }  
175            } else {  
176                myPrintln("ADS7830 device not detected at address 0x" + Integer.toHexString(ADC_CHIP_ADDR));  
177            }  
178        } finally {  
179            pi4j.shutdown();  
180        }  
181    }  
182}

Initialize the pins that control the RGB LED.

1int[] LED_PINS = {17, 27, 22}; 
2for (int pin : LED_PINS) {  
3    setPwmConfig(pin);  
4} 
5PWMController red_led = pwmControllers.get(LED_PINS[0]); 
6PWMController green_led = pwmControllers.get(LED_PINS[1]); 
7PWMController blue_led = pwmControllers.get(LED_PINS[2]); 

Get the ADC values corresponding to the 3 rotentiometers every 100 milliseconds; convert the values into duty cycle values corresponding to PWM, and print prompt information on the terminal.

 1while (true) {  
 2    int val_Red = adc.analogRead(ADC_CHN[0]);  
 3    int val_Green = adc.analogRead(ADC_CHN[1]); 
 4    int val_Blue = adc.analogRead(ADC_CHN[2]); 
 5
 6    red_led.setPwmDutyCycle(1-(double)(val_Red/255.0));
 7    green_led.setPwmDutyCycle(1-(double)(val_Green/255.0));
 8    blue_led.setPwmDutyCycle(1-(double)(val_Blue/255.0));
 9
10    myPrintln("ADC value val_Red:%d, val_Green:%d, val_Blue:%d", val_Red, val_Green, val_Blue);
11    Thread.sleep(100);  
12}