Beginner Analog

Analog Input Reading

Read analog sensors and convert raw values to meaningful measurements

Overview

Analog inputs allow you to read continuously varying signals from sensors like potentiometers, temperature sensors, light sensors, and pressure sensors. This example covers:

  • ADC configuration - Setting up pins for analog input
  • Raw value reading - Getting 12-bit ADC values (0-4095)
  • Voltage conversion - Converting to actual voltage
  • Sensor calibration - Converting to physical units
  • Filtering and averaging - Reducing noise

Hardware Setup

Potentiometer

  • • 10kΩ linear potentiometer
  • • Pin 1 → +3.3V
  • • Pin 2 → PoKeys analog pin 3
  • • Pin 3 → GND

Temperature Sensor

  • • LM35 temperature sensor
  • • VCC → +3.3V
  • • OUT → PoKeys analog pin 7
  • • GND → GND

Basic Analog Reading

use pokeys_lib::*;
use std::{thread, time::Duration};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("📊 PoKeys Analog Input Example");
    println!("==============================");
    
    // Connect to device
    let device_count = enumerate_usb_devices()?;
    if device_count == 0 {
        println!("❌ No PoKeys devices found!");
        return Ok(());
    }
    
    let mut device = connect_to_device(0)?;
    println!("✅ Connected to: {}", device.get_device_name()?);
    
    // Configure pins for analog input
    device.set_pin_function(3, PinFunction::AnalogInput)?;
    device.set_pin_function(7, PinFunction::AnalogInput)?;
    println!("⚙️  Configured pins 3 and 7 for analog input");
    
    // Set ADC reference voltage (3.3V for most PoKeys devices)
    device.set_analog_reference_voltage(3.3)?;
    
    println!("📈 Starting continuous reading (Ctrl+C to stop)...");
    println!("Pin 3 (Pot) | Pin 7 (Temp) | Voltage | Temperature");
    println!("------------|--------------|---------|------------");
    
    loop {
        // Read raw ADC values (0-4095 for 12-bit ADC)
        let pot_raw = device.get_analog_input(3)?;
        let temp_raw = device.get_analog_input(7)?;
        
        // Convert to voltage (0-3.3V)
        let pot_voltage = raw_to_voltage(pot_raw, 3.3);
        let temp_voltage = raw_to_voltage(temp_raw, 3.3);
        
        // Convert temperature sensor voltage to Celsius (LM35: 10mV/°C)
        let temperature_c = temp_voltage * 100.0;
        
        println!("{:>11} | {:>12} | {:>7.3}V | {:>8.1}°C", 
                 pot_raw, temp_raw, pot_voltage, temperature_c);
        
        thread::sleep(Duration::from_millis(500));
    }
}

/// Convert raw ADC value to voltage
fn raw_to_voltage(raw_value: u16, reference_voltage: f32) -> f32 {
    (raw_value as f32 / 4095.0) * reference_voltage
}

Advanced Sensor Reading with Filtering

use pokeys_lib::*;
use std::{collections::VecDeque, thread, time::Duration};

struct AnalogSensor {
    pin: u8,
    name: String,
    samples: VecDeque<u16>,
    max_samples: usize,
    calibration_offset: f32,
    calibration_scale: f32,
}

impl AnalogSensor {
    fn new(pin: u8, name: &str, max_samples: usize) -> Self {
        Self {
            pin,
            name: name.to_string(),
            samples: VecDeque::new(),
            max_samples,
            calibration_offset: 0.0,
            calibration_scale: 1.0,
        }
    }
    
    /// Add a new sample and maintain rolling average
    fn add_sample(&mut self, raw_value: u16) {
        self.samples.push_back(raw_value);
        if self.samples.len() > self.max_samples {
            self.samples.pop_front();
        }
    }
    
    /// Get filtered (averaged) raw value
    fn get_filtered_raw(&self) -> f32 {
        if self.samples.is_empty() {
            return 0.0;
        }
        let sum: u32 = self.samples.iter().map(|&x| x as u32).sum();
        sum as f32 / self.samples.len() as f32
    }
    
    /// Get calibrated value in physical units
    fn get_calibrated_value(&self, reference_voltage: f32) -> f32 {
        let voltage = (self.get_filtered_raw() / 4095.0) * reference_voltage;
        (voltage + self.calibration_offset) * self.calibration_scale
    }
    
    /// Set calibration parameters
    fn set_calibration(&mut self, offset: f32, scale: f32) {
        self.calibration_offset = offset;
        self.calibration_scale = scale;
    }
}

fn advanced_sensor_reading() -> Result<(), Box<dyn std::error::Error>> {
    let mut device = connect_to_device(0)?;
    
    // Configure analog inputs
    device.set_pin_function(3, PinFunction::AnalogInput)?;
    device.set_pin_function(7, PinFunction::AnalogInput)?;
    device.set_pin_function(8, PinFunction::AnalogInput)?;
    device.set_analog_reference_voltage(3.3)?;
    
    // Create sensor objects with filtering
    let mut potentiometer = AnalogSensor::new(3, "Potentiometer", 10);
    let mut temperature = AnalogSensor::new(7, "Temperature", 20);
    let mut light_sensor = AnalogSensor::new(8, "Light Level", 15);
    
    // Set calibrations
    temperature.set_calibration(0.0, 100.0); // LM35: 10mV/°C
    light_sensor.set_calibration(0.0, 100.0); // Convert to percentage
    potentiometer.set_calibration(0.0, 100.0); // Convert to percentage
    
    println!("🔬 Advanced Sensor Reading with Filtering");
    println!("==========================================");
    
    // Collect initial samples for filtering
    println!("📊 Collecting initial samples...");
    for _ in 0..25 {
        potentiometer.add_sample(device.get_analog_input(3)?);
        temperature.add_sample(device.get_analog_input(7)?);
        light_sensor.add_sample(device.get_analog_input(8)?);
        thread::sleep(Duration::from_millis(50));
    }
    
    println!("📈 Filtered sensor readings:");
    println!("Time     | Pot (%) | Temp (°C) | Light (%)");
    println!("---------|---------|-----------|----------");
    
    for i in 0..100 {
        // Read new samples
        potentiometer.add_sample(device.get_analog_input(3)?);
        temperature.add_sample(device.get_analog_input(7)?);
        light_sensor.add_sample(device.get_analog_input(8)?);
        
        // Get calibrated values
        let pot_percent = potentiometer.get_calibrated_value(3.3);
        let temp_celsius = temperature.get_calibrated_value(3.3);
        let light_percent = light_sensor.get_calibrated_value(3.3);
        
        println!("{:>8} | {:>7.1} | {:>9.1} | {:>8.1}", 
                 i, pot_percent, temp_celsius, light_percent);
        
        thread::sleep(Duration::from_millis(200));
    }
    
    Ok(())
}

Sensor Calibration

Different sensors require different calibration approaches:

Linear Sensors

// LM35 Temperature: 10mV/°C
let temp_c = voltage * 100.0;

// Pressure sensor: 0.5-4.5V = 0-100 PSI
let pressure = (voltage - 0.5) * (100.0 / 4.0);

// Potentiometer: 0-3.3V = 0-100%
let percentage = (voltage / 3.3) * 100.0;

Non-Linear Sensors

// Thermistor (requires lookup table)
let resistance = 10000.0 * voltage / (3.3 - voltage);
let temp_k = 1.0 / (A + B * resistance.ln() + 
                    C * resistance.ln().powi(3));
let temp_c = temp_k - 273.15;

// Light sensor (logarithmic response)
let lux = 10.0_f32.powf((voltage - 1.0) * 2.0);

Troubleshooting

Noisy readings

  • • Use averaging/filtering (moving average)
  • • Add capacitor across sensor (10-100nF)
  • • Keep analog wires short and shielded
  • • Use twisted pair cables for long runs

Readings stuck at 0 or 4095

  • • Check sensor power supply connections
  • • Verify sensor output voltage range
  • • Ensure pin is configured as analog input
  • • Check for short circuits

Inaccurate readings

  • • Calibrate with known reference values
  • • Check ADC reference voltage setting
  • • Account for sensor temperature drift
  • • Use proper sensor excitation voltage

Next Steps