Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Last updated: Wednesday 29 April 2026 @ 11:05:00

ADC2PWM

Like we did for C_Library/ADCLibrary/PWMLibrary/ADCtoPWM we are going to combine the adc.rs and and pwm.rs capabilities and examples/adc_read.rs and examples/pwm_test.rs.

Mapping adc to pwm

  1. To map we need to get the relationship between adc and the period to get the duty cycle, as seen below:

    \[ \text{duty_cycle_ns} = \frac{\text{adc_value} \cdot \text{period_ns}}{4095} \]

  2. This has been implemented in the pwm.rs in the last chapter, for reference here is code snippet for the function:

    Code

    pub fn map_adc_to_duty_cycle(adc_value: u16, period_ns: u32) -> BbbResult<u32> {
        let adc_value = adc_value as u32;
        if adc_value > MAX_ADC {
            return Err(BbbError::InvalidValue(format!(
                "ADC value out of range: {adc_value}"
            )));
        }
        Ok((adc_value * period_ns) / MAX_ADC)
    }
    

We are going to create a script that utlises what we did for ~/examples/adc.rs and examples/pwm_test.rs.

  1. Create a file called ~/bbb-rust/examples/adc2pwm.rs and reproduce the following code:

    Code: examples/adc2pwm.rs [44 lines]

    use std::thread;
    use std::time::Duration;
    
    use bbb_io::adc::{self, AdcPin};
    use bbb_io::pwm::{self, Pwm};
    
    fn main() -> bbb_io::BbbResult<()> {
        let pwm = Pwm::init("P9_14")?;
    
        let period_ns = 1_000_000; // 1 ms = 1 kHz
    
        pwm.set_period(period_ns)?;
        pwm.set_duty_cycle(0)?;
        pwm.enable()?;
    
        println!("Phy: {}", pwm.physical_pin);
        println!("Channel: {}", pwm.channel);
        println!("Chip: {}", pwm.chip);
        println!("Period path: {}", pwm.period_path.display());
        println!("Duty Cycle path: {}", pwm.duty_cycle_path.display());
        println!("Enable path: {}", pwm.enable_path.display());
    
        for _ in 0..30 {
            let raw = adc::read_raw(AdcPin::A0)?;
    
            let mut volts = 0.0;
            adc::to_voltage(raw, &mut volts)?;
    
            let duty_cycle = pwm::map_adc_to_duty_cycle(raw as u16, period_ns)?;
    
            println!(
                "ADC Reading: {raw:4}, Voltage: {volts:.3} V, Duty Cycle: {duty_cycle} ns"
            );
    
            pwm.set_duty_cycle(duty_cycle)?;
    
            thread::sleep(Duration::from_secs(1));
        }
    
        pwm.disable()?;
        pwm.cleanup()?;
    
        Ok(())
    }
    

    Explanation

    This example uses the IIO buffer interface rather than repeatedly reading in_voltage0_raw.

    The key line is:

    let mut adc = BufferedAdc::open(AdcPin::A0, 16)?;
    

    This configures the kernel buffer and opens /dev/iio:device0.

    The loop then repeatedly reads binary samples:

    let raw = adc.read_raw()?;
    

    and converts each value to voltage:

    adc::to_voltage(raw, &mut voltage)?;
    

    The buffer is configured once, then reused:

    open buffer once
    read many samples
    cleanup automatically
    
  2. Build and run the program:

    Terminal

    rustc examples/adc2pwm.rs \
    --edition=2021 \
    -L target/debug \
    --extern bbb_io=target/debug/libbbb_io.rlib \
    -o adc2pwm
    
  3. Setup the following circuit, which is an amalgamation of ADC and PWM circuits


    Danger

    The BeagleBone Black ADC input range is 0 V to 1.8 V.

    Do not connect 3.3 V directly to an ADC input pin.

    If using a 3.3 V source, use a voltage divider or a safe external 1.8 V reference.

    BBB Headers

  4. When running ./adc2pwm you may see this terminal output and you have connected up your circuity correctly, the LED should change it’s brightness based on the resistance from the potentiometer:

    If you see:

    Error

    Error: Io(Os { code: 16, kind: ResourceBusy, message: "Device or resource busy" })
    

    Terminal

    echo 0 | sudo tee /sys/bus/iio/devices/iio\:device0/buffer/enable 
    

    and then re-run the program.

    or:

    Tip

    You may need elevated permissions to configure or read from the IIO buffer depending on your system permissions:

    sudo ./adc_continuous_read
    

    Output

    Phy: P9_14
    Channel: 0
    Chip: 0
    Period path: /sys/class/pwm/pwmchip0/pwm0/period
    Duty Cycle path: /sys/class/pwm/pwmchip0/pwm0/duty_cycle
    Enable path: /sys/class/pwm/pwmchip0/pwm0/enable
    ADC Reading: 1536, Voltage: 0.675 V, Duty Cycle: 375091 ns
    ADC Reading: 1537, Voltage: 0.675 V, Duty Cycle: 375335 ns
    ADC Reading: 1536, Voltage: 0.675 V, Duty Cycle: 375091 ns
    ADC Reading:  626, Voltage: 0.275 V, Duty Cycle: 152869 ns
    ADC Reading:  258, Voltage: 0.113 V, Duty Cycle: 63003 ns
    ADC Reading:    0, Voltage: 0.000 V, Duty Cycle: 0 ns
    ADC Reading:    0, Voltage: 0.000 V, Duty Cycle: 0 ns
    ADC Reading:  308, Voltage: 0.135 V, Duty Cycle: 75213 ns
    ADC Reading:  635, Voltage: 0.279 V, Duty Cycle: 155067 ns
    ADC Reading: 1078, Voltage: 0.474 V, Duty Cycle: 263247 ns
    ADC Reading: 1456, Voltage: 0.640 V, Duty Cycle: 355555 ns
    ADC Reading: 2093, Voltage: 0.920 V, Duty Cycle: 511111 ns
    ADC Reading: 2565, Voltage: 1.127 V, Duty Cycle: 626373 ns
    ADC Reading: 3390, Voltage: 1.490 V, Duty Cycle: 827838 ns
    ADC Reading: 4023, Voltage: 1.768 V, Duty Cycle: 982417 ns
    ADC Reading: 4094, Voltage: 1.799 V, Duty Cycle: 999755 ns
    ADC Reading: 4094, Voltage: 1.799 V, Duty Cycle: 999755 ns
    ...
    
  5. Once you have done that, experiment with the code, can you get a faster response, or a longer run time of the program?