Thank you for your donation!


Cloudsmith graciously provides open-source package management and distribution for our project.


Instruction Guide Volume change with analog potentiometer
#1
Thumbs Up 
Hello! 
Wrote F.A.Q. on request Drone7
To use an analog potentiometer as a volume control with a raspberry pi you will need:
- ADC, I used ADS1115 link
- analog POT, I used the simplest link
We increase or decrease the system volume using the command-line program 

Code:
amixer
Install the ADS1115 ADC in the following order (source - link):
1.  Enable I2C on the Raspberry Pi using raspi-config
Code:
sudo apt-get install -y python-smbus
sudo apt-get install -y i2c-tools
Run 

Code:
sudo raspi-config
and follow the prompts to install i2c support for the ARM core and linux kernel

Go to Interfacing Options, then I2C -> Enable! –> Yes
Once this is all done, reboot!

Code:
sudo reboot

2. Testing i2c

Code:
sudo i2cdetect -y 1
This shows that I2C address ADC

3. Connect the ADC to the Pi as follows:

-     ADS1x15 VDD to Raspberry Pi 3.3V
-     ADS1x15 GND to Raspberry Pi GND
-     ADS1x15 SCL to Raspberry Pi SCL
-    ADS1x15 SDA to Raspberry Pi SDA

4. Source Install
To install from the source on Github connect to a terminal on the Raspberry Pi and run the following commands:

Code:
sudo apt-get update
sudo apt-get install build-essential python-dev python-smbus git
cd ~
git clone https://github.com/adafruit/Adafruit_Python_ADS1x15.git
cd Adafruit_Python_ADS1x15
sudo python setup.py install


5. Connecting an analog potentiometer
It goes according to the following scheme:
Left leg - GND
Central leg - A1 ADS1115
Right leg - VCC 3.3 v


6. Check potentiometer connection
Go to the folder 
examples:


Code:
cd ~/Adafruit_Python_ADS1x15/examples
run the simpletest.py

Code:
sudo python simpletest.py
When rotating the potentiometer knob, the values of channel 1 should change. Check that there are no negative values in the leftmost position (readings 0-2). Otherwise, check the quality of connections, it is better to collect on the soldering.
Write down the value in the extreme right position (required to correct the code). I have - 26274


Now set the code for the operation of the potentiometer, the manual is taken as the basislink

First, make sure amixer is present and install it if it isn’t.

Code:
which amixer || sudo apt-get install alsa-utils

You might not have Python 3; if which python3 turns up nothing, try this:

Code:
sudo apt-get install python3
in home / pi, create a python file with any name (in my case it is volknob.py) and copy this code into it

Code:
#!/usr/bin/python
# coding=utf-8
#############################################################################################################
### Copyright by Joy-IT

### Published under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License

### Commercial use only after permission is requested and granted
### Programme traduit par Go tronic

###
### Single Analog Sensor - Raspberry Pi Python Code Example

###
#############################################################################################################


# Ce code utilise les librairies Python ADS1115 et I2C pour la Raspberry Pi

# Ces librairies sont publiées sous licence BSD sur le lien ci-dessous

# [https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code]
import Adafruit_ADS1x15
from time import sleep

# Les modules nécessaires sont importés et mis en place

import time, signal, os, math
import logging
import signal
import subprocess
import sys

# Les variables utilisées sont initialisées
delayTime = 0.2
voltageMax = 3300
# valeur de tension maximale possible à l'entrée du convertisseur ADC

# attribution d'adresse ADS1x15 ADC

ADS1015 = 0x00 # 12-bit ADC
ADS1115 = 0x01 # 16-bit ADC

# Choix du gain

gain = 1  # +/- 4.096V

# gain = 2048  # +/- 2.048V

# gain = 1024  # +/- 1.024V

# gain = 512   # +/- 0.512V

# gain = 256 # +/- 0.256V


# Choix de la fréquence d'échantillonnage ADC (SampleRate)

# sps = 8    # 8 échantillons par seconde

# sps = 16   # 16 échantillons par seconde

# sps = 32 # 32 échantillons par seconde

# sps = 64   # 64 échantillons par seconde

# sps = 128 # 128 échantillons par seconde

sps = 250  # 250 échantillons par seconde

# sps = 860 # 860 échantillons par seconde


# choix du canal ADC (1-4)

# adc_channel = 0    # Channel 0

adc_channel = 1    # Channel 1

# adc_channel = 2    # Channel 2

# adc_channel = 3 # Channel 3


# initialisation du convertisseur

adc = Adafruit_ADS1x15.ADS1115()


#############################################################################################################


# ########
# boucle de programme principale
# ########
# Le programme mesure la tension à l'aide du convertisseur ADS1115.

# Il calcule la résistance de la LDR et les transmet à la console.


logger = logging.getLogger(__name__)

# logging
logging.basicConfig(
   level=logging.DEBUG,
   format='%(asctime)-15s - %(message)s'
)
# SETTINGS
# ========

DEBUG = True


# The minimum and maximum volumes, as percentages.
#
# The default max is less than 100 to prevent distortion. The default min is
# greater than zero because if your system is like mine, sound gets
# completely inaudible _long_ before 0%. If you've got a hardware amp or
# serious speakers or something, your results will vary.
VOLUME_MIN = 0
VOLUME_MAX = 100

# The amount you want one click of the knob to increase or decrease the
# volume. I don't think that non-integer values work here, but you're welcome
# to try.
VOLUME_INCREMENT = 1

# Audio device name, e.g. 'PCM' or 'Master'. Find with: amixer scontrols
DEVICE_NAME = 'PCM'

# (END SETTINGS)
#


def debug(str):
   if not DEBUG:
       return
   logger.debug(str)


class RotaryEncoder(object):
   """
   A class to decode mechanical rotary encoder pulses.
   """
   def __init__(self, channel, tolerance=5):
       self.adc = Adafruit_ADS1x15.ADS1115()
       self.volume = Volume()
       self.channel = adc_channel

       # to keep from being jittery we'll only change
       # volume when the pot has moved more than 5 'counts'
       self.tolerance = tolerance

       # this keeps track of the last potentiometer value
       self.last_read = 0

   def destroy(self):
       debug('Destroying...')

   def read(self):
       # we'll assume that the pot didn't move
       trim_pot_changed = False

       # read the analog pin
       trim_pot = self.adc.read_adc(self.channel, gain, sps)

       # how much has it changed since the last read?
       pot_adjust = abs(trim_pot - self.last_read)

       if pot_adjust > self.tolerance:
           trim_pot_changed = True

       if trim_pot_changed:
           # convert trim pot read into 0-100 volume level
           set_volume = int(round(trim_pot / 262.74))
           self.volume.set_volume(set_volume)

           # save the potentiometer reading for the next loop
           self.last_read = trim_pot

class VolumeError(Exception):
   pass

class Volume(object):
   """
   A wrapper API for interacting with the volume settings on the RPi.
   """
   MIN = VOLUME_MIN
   MAX = VOLUME_MAX
   INCREMENT = VOLUME_INCREMENT

   def __init__(self):
       # Set an initial value for last_volume in case we're muted when we start.
       self.last_volume = self.MIN
       self._sync()

   def up(self):
       """
       Increases the volume by one increment.
       """
       return self.change(self.INCREMENT)

   def down(self):
       """
       Decreases the volume by one increment.
       """
       return self.change(-self.INCREMENT)

   def change(self, delta):
       v = self.volume + delta
       v = self._constrain(v)
       return self.set_volume(v)

   def set_volume(self, v):
       """
       Sets volume to a specific value.
       """
       self.volume = self._constrain(v)
       debug("set volume: {}".format(self.volume))

       output = self.amixer("set -M '{}' unmute {}%".format(DEVICE_NAME, v))
       self._sync(output)
       return self.volume
   
   def toggle(self):
       """
       Toggles muting between on and off.
       """
       if self.is_muted:
           output = self.amixer("set -M '{}' unmute".format(DEVICE_NAME))
       else:
           # We're about to mute ourselves, so we should remember the last volume
           # value we had because we'll want to restore it later.
           self.last_volume = self.volume
           output = self.amixer("set -M '{}' mute".format(DEVICE_NAME))

       self._sync(output)
       if not self.is_muted:
           # If we just unmuted ourselves, we should restore whatever volume we
           # had previously.
           self.set_volume(self.last_volume)
       return self.is_muted

   def status(self):
       if self.is_muted:
           return "{}% (muted)".format(self.volume)
       return "{}%".format(self.volume)

   # Read the output of `amixer` to get the system volume and mute state.
   #
   # This is designed not to do much work because it'll get called with every
   # click of the knob in either direction, which is why we're doing simple
   # string scanning and not regular expressions.
   def _sync(self, output=None):
       if output is None:
           output = self.amixer("get -M '{}'".format(DEVICE_NAME))

       lines = output.readlines()
       if DEBUG:
           strings = [line.decode('utf8') for line in lines]
           debug("OUTPUT:")
           debug("".join(strings))
       last = lines[-1].decode('utf-8')
       # The last line of output will have two values in square brackets. The
       # first will be the volume (e.g., "[95%]") and the second will be the
       # mute state ("[off]" or "[on]").
       i1 = last.rindex('[') + 1
       i2 = last.rindex(']')

       self.is_muted = last[i1:i2] == 'off'

       i1 = last.index('[') + 1
       i2 = last.index('%')
       # In between these two will be the percentage value.
       pct = last[i1:i2]

       self.volume = int(pct)

   # Ensures the volume value is between our minimum and maximum.
   def _constrain(self, v):
       if v < self.MIN:
           return self.MIN
       if v > self.MAX:
           return self.MAX
       return v

   def amixer(self, cmd):
       p = subprocess.Popen("amixer {}".format(cmd), shell=True, stdout=subprocess.PIPE)
       code = p.wait()
       if code != 0:
           raise VolumeError("Unknown error: {}".format(code))
           sys.exit(0)

       return p.stdout


if __name__ == "__main__":
   def on_exit(a, b):
       debug("Exiting...")
       encoder.destroy()
       sys.exit(0)

   encoder = RotaryEncoder( adc_channel)
   signal.signal(signal.SIGINT, on_exit)

   debug("Initial volume: {}".format(encoder.volume.volume))

   while True:
       encoder.read()

       # hang out and do nothing for a half second
       time.sleep(0.5)
In the code variables are the following values:
VOLUME_MIN = 0
VOLUME_MAX = 100
VOLUME_INCREMENT = 1

# Audio device name, e.g. 'PCM’, 'Master' or your sound card. Find with: amixer scontrols
DEVICE_NAME = 'PCM'

# convert trim pot read into 0-100 volume level
set_volume = int(round(trim_pot / 262.74)) – specify the value from clause 6/100

Next we create an autorun service (described on the author’s code page) and enjoy. Wink

P.s. translation via google, code copied from Putty, could be wrong
Reply
#2
Hello Fedormil,

I tried to use your python script on my MOODE 9.1.3 installation: it seems that it does not work anymore on the new bookworm system...

Could you try to update it ? I would like to replace my encoder with a potentiometer (I bought an ADC ADS1115), but my trials to modify your script are currently unsuccessful.

Thank you in advance for the support you may bring.
Reply


Forum Jump: